diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md
index 28b00e28..b438c642 100644
--- a/packages/ui/CHANGELOG.md
+++ b/packages/ui/CHANGELOG.md
@@ -1,5 +1,11 @@
# @soramitsu-ui/ui
+## 0.13.13
+
+### Patch Changes
+
+- b5029a5b: **feat**(`SSelectDropdown`): add mandatory prop
+
## 0.13.12
### Patch Changes
diff --git a/packages/ui/cypress/component/Select.spec.cy.ts b/packages/ui/cypress/component/Select.spec.cy.ts
index 25af419c..7eba67a6 100644
--- a/packages/ui/cypress/component/Select.spec.cy.ts
+++ b/packages/ui/cypress/component/Select.spec.cy.ts
@@ -244,6 +244,127 @@ it('SSelect - clicking options, checking auto-transformations', () => {
assertValue('2')
})
+it('SSelect simple options mandatory mode works', () => {
+ cy.mount({
+ setup() {
+ const model = ref(null)
+
+ const options = [
+ { label: 'Opt 1', value: 1 },
+ { label: 'Opt 2', value: 2 },
+ { label: 'Opt 3', value: 3 },
+ ]
+
+ const [multiple, toggleMultiple] = useToggle(false)
+
+ return { model, options, multiple, toggleMultiple }
+ },
+ template: `
+
Value: {{ model }}
+
+
+ `,
+ })
+
+ function assertValue(val: string) {
+ cy.contains(`Value: ${val}`)
+ }
+
+ cy.contains('Dap').click()
+ cy.get('.s-select-option__content').eq(0).click()
+ assertValue('1')
+
+ cy.contains('Dap').click()
+ cy.get('.s-select-option__content').eq(0).click()
+ assertValue('1')
+
+ cy.get('button').contains('multiple').click()
+ cy.contains('Dap').click()
+ cy.get('.s-select-option__content').eq(1).click()
+ assertValue('[ 1, 2 ]')
+
+ cy.get('.s-select-option__content').eq(1).click()
+ cy.get('.s-select-option__content').eq(0).click()
+ assertValue('[ 1 ]')
+})
+
+it('SSelect group options mandatory mode works', () => {
+ cy.mount({
+ setup() {
+ const model = ref(null)
+
+ const options = [
+ {
+ header: '1st group',
+ selectAllBtn: true,
+ items: [
+ {
+ label: 'Germany',
+ value: 'du',
+ },
+ {
+ label: 'England',
+ value: 'en',
+ },
+ {
+ label: 'United Arab Emirates',
+ value: 'ae',
+ },
+ ],
+ },
+ {
+ header: '2nd group',
+ selectAllBtn: false,
+ items: [
+ {
+ label: 'Iceland',
+ value: 'is',
+ },
+ {
+ label: 'Japan',
+ value: 'jp',
+ },
+ ],
+ },
+ ]
+
+ return { model, options }
+ },
+ template: `
+ Value: {{ model }}
+
+ `,
+ })
+
+ function assertValue(val: string) {
+ cy.contains(`Value: ${val}`)
+ }
+
+ cy.contains('Dap').click()
+
+ cy.get('.s-select-dropdown__action').click()
+ assertValue(`[ "du", "en", "ae" ]`)
+ cy.get('.s-select-dropdown__action').click()
+ assertValue(`[ "du", "en", "ae" ]`)
+ cy.get('.s-select-option__content').eq(3).click()
+ assertValue(`[ "du", "en", "ae", "is" ]`)
+ cy.get('.s-select-dropdown__action').click()
+ assertValue(`[ "is" ]`)
+ cy.get('.s-select-option__content').eq(3).click()
+ assertValue(`[ "is" ]`)
+})
+
it('SDropdown - model usage works', () => {
cy.mount({
setup() {
diff --git a/packages/ui/etc/api/ui.api.md b/packages/ui/etc/api/ui.api.md
index 39257739..5caef6ad 100644
--- a/packages/ui/etc/api/ui.api.md
+++ b/packages/ui/etc/api/ui.api.md
@@ -648,6 +648,7 @@ loading?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
+mandatory?: boolean | undefined;
}>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, VNodeProps & AllowedComponentProps & ComponentCustomProps, Readonly[] | SelectOptionGroup[] | undefined;
@@ -662,12 +663,14 @@ loading?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
+mandatory?: boolean | undefined;
}>>>, {}, {}>, {
label?(_: {
options: UnwrapRef[] | SelectOptionGroup[]>;
multiple: boolean;
disabled: boolean;
loading: boolean;
+ mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
@@ -703,6 +706,8 @@ export interface SelectApi extends UnwrapRef> {
readonly label: string | null;
// (undocumented)
readonly loading: boolean;
+ // (undocumented)
+ readonly mandatory: boolean;
menuToggle: (value?: boolean) => void;
// (undocumented)
readonly multiple: boolean;
@@ -1284,6 +1289,7 @@ triggerSearch?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
+mandatory?: boolean | undefined;
}>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, VNodeProps & AllowedComponentProps & ComponentCustomProps, Readonly[] | SelectOptionGroup[] | undefined;
@@ -1298,12 +1304,14 @@ triggerSearch?: boolean | undefined;
dropdownSearch?: boolean | undefined;
remoteSearch?: boolean | undefined;
maxShownOptions?: string | number | undefined;
+mandatory?: boolean | undefined;
}>>>, {}, {}>, {
label?(_: {
options: UnwrapRef[] | SelectOptionGroup[]>;
multiple: boolean;
disabled: boolean;
loading: boolean;
+ mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
@@ -1358,6 +1366,7 @@ sameWidthPopper: boolean;
triggerSearch: boolean;
dropdownSearch: boolean;
remoteSearch: boolean;
+mandatory: boolean;
}>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
"update:modelValue": (value: any) => void;
search: (value: string) => void;
@@ -1390,6 +1399,7 @@ sameWidthPopper: boolean;
triggerSearch: boolean;
dropdownSearch: boolean;
remoteSearch: boolean;
+mandatory: boolean;
}>>> & {
"onUpdate:modelValue"?: ((value: any) => any) | undefined;
onSearch?: ((value: string) => any) | undefined;
@@ -1401,6 +1411,7 @@ size: SelectSize;
disabled: boolean;
loading: boolean;
options: SelectOption[] | SelectOptionGroup[];
+mandatory: boolean;
syncMenuAndInputWidths: boolean;
noAutoClose: boolean;
sameWidthPopper: boolean;
@@ -1437,6 +1448,7 @@ type: SelectButtonType;
multiple: boolean;
disabled: boolean;
loading: boolean;
+ mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
@@ -1482,6 +1494,7 @@ search: boolean;
multiple: boolean;
disabled: boolean;
loading: boolean;
+ mandatory: boolean;
label: string | null;
size: SelectSize;
noAutoClose: boolean;
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 2dc03454..212eb0ae 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@soramitsu-ui/ui",
- "version": "0.13.12",
+ "version": "0.13.13",
"main": "dist/lib.cjs",
"module": "dist/lib.mjs",
"types": "dist/lib.d.ts",
diff --git a/packages/ui/src/components/Select/SDropdown.vue b/packages/ui/src/components/Select/SDropdown.vue
index b3057382..2d25ec45 100644
--- a/packages/ui/src/components/Select/SDropdown.vue
+++ b/packages/ui/src/components/Select/SDropdown.vue
@@ -19,6 +19,7 @@ const props = defineProps<{
dropdownSearch?: boolean
remoteSearch?: boolean
maxShownOptions?: string | number | undefined
+ mandatory?: boolean
}>()
const buttonType = computed(() => (props.inline ? SelectButtonType.Inline : SelectButtonType.Default))
diff --git a/packages/ui/src/components/Select/SSelect.vue b/packages/ui/src/components/Select/SSelect.vue
index ed65b154..77110e21 100644
--- a/packages/ui/src/components/Select/SSelect.vue
+++ b/packages/ui/src/components/Select/SSelect.vue
@@ -19,6 +19,7 @@ const props = defineProps<{
dropdownSearch?: boolean
remoteSearch?: boolean
maxShownOptions?: string | number | undefined
+ mandatory?: boolean
}>()
const defaultOptionType = computed(() => (props.multiple ? SelectOptionType.Checkbox : SelectOptionType.Radio))
diff --git a/packages/ui/src/components/Select/SSelectBase.vue b/packages/ui/src/components/Select/SSelectBase.vue
index 19f96f7d..acec8b9b 100644
--- a/packages/ui/src/components/Select/SSelectBase.vue
+++ b/packages/ui/src/components/Select/SSelectBase.vue
@@ -23,10 +23,9 @@ const props = withDefaults(
label?: string | null
/**
- * TODO
- *
* - Doesn't allow to unselect value in single mode
* - Doesn't allow to unselect last the only one picked value in multiple mode
+ * TODO
* - Picks some value automatically (the first one) if `modelValue` is null and there are some available options
*/
mandatory?: boolean
@@ -83,6 +82,7 @@ const props = withDefaults(
triggerSearch: false,
dropdownSearch: false,
remoteSearch: false,
+ mandatory: false,
},
)
@@ -92,7 +92,7 @@ const emit = defineEmits<{
}>()
const model = useVModel(props, 'modelValue', emit)
-const { multiple, disabled, loading, options, size, label, noAutoClose, remoteSearch } = toRefs(props)
+const { multiple, disabled, loading, options, size, label, noAutoClose, remoteSearch, mandatory } = toRefs(props)
const modeling = useSelectModel({
model,
@@ -101,6 +101,7 @@ const modeling = useSelectModel({
storeSelectedOptions: remoteSearch,
singleModeAutoClose: not(noAutoClose),
onAutoClose: () => togglePopper(false),
+ mandatory,
})
const [showPopper, togglePopper] = useToggle(false)
@@ -125,6 +126,7 @@ const api: SelectApi = reactive({
options,
disabled,
loading,
+ mandatory,
label,
isMenuOpened: showPopper,
menuToggle: togglePopper,
diff --git a/packages/ui/src/components/Select/api.ts b/packages/ui/src/components/Select/api.ts
index 5c139e8a..c34e2b82 100644
--- a/packages/ui/src/components/Select/api.ts
+++ b/packages/ui/src/components/Select/api.ts
@@ -8,6 +8,7 @@ export interface SelectApi extends UnwrapRef> {
readonly multiple: boolean
readonly disabled: boolean
readonly loading: boolean
+ readonly mandatory: boolean
readonly label: string | null
readonly size: SelectSize
readonly noAutoClose: boolean
diff --git a/packages/ui/src/components/Select/use-model.ts b/packages/ui/src/components/Select/use-model.ts
index 1d1941ae..a1c5c4ee 100644
--- a/packages/ui/src/components/Select/use-model.ts
+++ b/packages/ui/src/components/Select/use-model.ts
@@ -42,6 +42,7 @@ export interface UseSelectModelParams {
* Should be used to actually perform menu closing
*/
onAutoClose: () => void
+ mandatory?: Ref
}
export function useSelectModel({
@@ -51,6 +52,7 @@ export function useSelectModel({
singleModeAutoClose,
onAutoClose,
storeSelectedOptions,
+ mandatory,
}: UseSelectModelParams): UseSelectModelReturn {
const triggerAutoClose = () => singleModeAutoClose.value && onAutoClose()
@@ -177,13 +179,25 @@ export function useSelectModel({
}
function toggleSelection(value: T): void {
- isValueSelected(value) ? unselect(value) : select(value)
+ if (!isValueSelected(value)) {
+ select(value)
+ } else {
+ if (
+ mandatory?.value &&
+ ((Array.isArray(model.value) && model.value.length === 1) || (model.value && !multiple.value))
+ )
+ return
+
+ unselect(value)
+ }
}
function toggleGroupSelection(optionGroup: SelectOptionGroup): void {
const optionGroupValues = optionGroup.items.map((x) => x.value)
if (isGroupSelected(optionGroup)) {
+ if (mandatory?.value && Array.isArray(model.value) && model.value.length === optionGroup.items.length) return
+
const optionGroupSet = new Set(optionGroupValues)
const newModel = modelAsArray.value.filter((x) => !optionGroupSet.has(x))
modelChangedManually = true
diff --git a/packages/ui/stories/components/select/common.ts b/packages/ui/stories/components/select/common.ts
index 1920e9a0..ee193ceb 100644
--- a/packages/ui/stories/components/select/common.ts
+++ b/packages/ui/stories/components/select/common.ts
@@ -69,6 +69,7 @@ export const COMMON_ARGS = {
noAutoClose: false,
dropdownSearch: false,
maxShownOptions: 0,
+ mandatory: false,
}
export const COMMON_ARG_TYPES = {