@itfin/components 1.3.43 → 1.3.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itfin/components",
3
- "version": "1.3.43",
3
+ "version": "1.3.44",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -0,0 +1,196 @@
1
+ <template>
2
+
3
+ <div class="b-condition-group-input w-100 pt-1 pb-2 px-2">
4
+ <div class="d-flex align-items-start">
5
+ <div v-if="value.conditions.length > 1" class="condition-group-type">
6
+ <itf-select
7
+ :value="value.link"
8
+ :reduce="(item) => item.value"
9
+ :options="[{ label: 'AND', value: 'and' }, { label: 'OR', value: 'or' }]"
10
+ @input="onLinkUpdate"
11
+ />
12
+ </div>
13
+ <div class="flex-1 pt-1">
14
+ <div v-for="(filter, n) in value.conditions" :key="n" :class="{'tree-node': value.conditions.length > 1}">
15
+ <div class="d-flex align-items-start">
16
+ <div class="flex-grow-1 py-2 me-2">
17
+ <itf-condition-group
18
+ v-if="filter.link"
19
+ :level="level + 1"
20
+ class="rounded"
21
+ :value="filter"
22
+ @input="onFilterUpdate(n, $event)"
23
+ :class="{'bg-group1': level === 0, 'bg-group2': level === 1}"
24
+ />
25
+ <filter-input
26
+ v-else
27
+ :value="filter"
28
+ @input="onFilterUpdate(n, $event)"
29
+ />
30
+ </div>
31
+ <div class="pt-2 mt-1">
32
+ <itf-button small icon @click="removeField(n)">
33
+ <itf-icon name="trash" />
34
+ </itf-button>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <itf-button small secondary @click="addCondition">
42
+ <itf-icon name="plus" />
43
+ Add condition
44
+ </itf-button>
45
+ <itf-button v-if="level < maxDepth" small secondary @click="addConditionGroup">
46
+ <itf-icon name="plus" />
47
+ Add group
48
+ </itf-button>
49
+ </div>
50
+
51
+ </template>
52
+ <style>
53
+ .b-condition-group-input {
54
+ --b-filterGroupBorder: rgba(0,0,0,.05);
55
+ --b-filterGroup1Bg: #f9fafb;
56
+ --b-filterGroup2Bg: #f1f2f4;
57
+
58
+ [data-theme="dark"] & {
59
+ --b-filterGroup1Bg: #303436;
60
+ --b-filterGroup2Bg: #3a3e41;
61
+ --b-filterGroupBorder: hsla(0,0%,100%,.03);
62
+ }
63
+ }
64
+ .bg-group1 {
65
+ background: var(--b-filterGroup1Bg);
66
+ border: 1px solid var(--b-filterGroupBorder);
67
+ }
68
+ .bg-group2 {
69
+ background: var(--b-filterGroup2Bg);
70
+ border: 1px solid var(--b-filterGroupBorder);
71
+ }
72
+ .condition-group-type {
73
+ width: 85px;
74
+ z-index: 10;
75
+ margin-right: 1rem;
76
+ margin-top: .5rem;
77
+ margin-bottom: .5rem;
78
+ position: relative;
79
+ }
80
+ .flex-1 {
81
+ flex: 1 1 0%;
82
+ }
83
+ .tree-node {
84
+ --h-line-position: 1.5rem;
85
+ --line-color: #D2D5DF;
86
+ --line-width: 1px;
87
+ --line-radius: 10px;
88
+ --line-connection-position: -3.5rem;
89
+ --node-space: .5rem;
90
+ margin-bottom: var(--node-space);
91
+ position: relative;
92
+
93
+ &:first-child:before {
94
+ border-left: solid var(--line-width) var(--line-color);
95
+ top: var(--h-line-position);
96
+ height: calc(100% - var(--h-line-position) + var(--node-space));
97
+ width: var(--line-radius);
98
+ }
99
+ &:before {
100
+ display: inline-block;
101
+ border-left: solid var(--line-width) var(--line-color);
102
+ content: "";
103
+ height: calc(100% + var(--node-space));
104
+ position: absolute;
105
+ left: var(--line-connection-position);
106
+ }
107
+ &:first-child:after {
108
+ height: var(--line-radius);
109
+ }
110
+ &:after {
111
+ display: inline-block;
112
+ border-top: solid var(--line-width) var(--line-color);
113
+ content: "";
114
+ width: calc(var(--line-connection-position)* -1);
115
+ position: absolute;
116
+ top: var(--h-line-position);
117
+ left: var(--line-connection-position);
118
+ }
119
+ &:last-child:before {
120
+ border-color: var(--line-color);
121
+ top: 0%;
122
+ height: var(--h-line-position);
123
+ width: var(--line-radius);
124
+ border-bottom-left-radius: var(--line-radius);
125
+ }
126
+ &:last-child:after {
127
+ border-top: none;
128
+ border-bottom: solid var(--line-width) var(--line-color);
129
+ border-left: solid var(--line-width) var(--line-color);
130
+ height: var(--line-radius);
131
+ border-bottom-left-radius: var(--line-radius);
132
+ top: calc(var(--h-line-position) - var(--line-radius));
133
+ }
134
+ }
135
+ </style>
136
+ <script>
137
+ import { Component, Prop, Model, Vue, Watch } from 'vue-property-decorator';
138
+ import itfButton from '@itfin/components/src/components/button/Button.vue';
139
+ import itfTextField from '@itfin/components/src/components/text-field/TextField.vue';
140
+ import itfDropdown from '@itfin/components/src/components/dropdown/Dropdown.vue';
141
+ import itfSelect from '@itfin/components/src/components/select/Select.vue';
142
+ import itfIcon from '@itfin/components/src/components/icon/Icon.vue';
143
+ import FilterInput from '~/components/Panels/FilterInput.vue';
144
+
145
+ export default @Component({
146
+ name: 'itfConditionGroup',
147
+ components: {
148
+ itfTextField,
149
+ itfSelect,
150
+ itfDropdown,
151
+ itfButton,
152
+ itfIcon,
153
+ FilterInput
154
+ }
155
+ })
156
+ class ConditionGroup extends Vue {
157
+ @Model('input', { default: () => ({ link: 'and', conditions: [] }) }) value;
158
+ @Prop({ default: 0 }) level;
159
+ @Prop({ default: 1 }) maxDepth;
160
+
161
+ onLinkUpdate(link) {
162
+ this.$emit('input', { ...this.value, link });
163
+ }
164
+
165
+ onFilterUpdate(index, filter) {
166
+ this.applyValue((conditions) => {
167
+ conditions[index] = filter;
168
+ });
169
+ }
170
+
171
+ applyValue(func) {
172
+ const newValue = { ...this.value, conditions: [...this.value.conditions] };
173
+ func(newValue.conditions);
174
+ this.$emit('input', newValue);
175
+ }
176
+
177
+ addCondition() {
178
+ this.applyValue((conditions) => {
179
+ conditions.push({ operator: 'is', values: [] });
180
+ });
181
+ }
182
+
183
+ addConditionGroup() {
184
+ this.applyValue((conditions) => {
185
+ conditions.push({ link: 'and', conditions: [] });
186
+ });
187
+ // this.filters.push({ type: 'group' });
188
+ }
189
+
190
+ removeField(index) {
191
+ this.applyValue((conditions) => {
192
+ conditions.splice(index, 1);
193
+ });
194
+ }
195
+ }
196
+ </script>
@@ -0,0 +1,64 @@
1
+ <template>
2
+
3
+ <div class="d-flex">
4
+ <div class="w-25">
5
+ <itf-select :options="fields" />
6
+ </div>
7
+ <div class="w-25 px-1">
8
+ <itf-select
9
+ :reduce="(item) => item.id"
10
+ :get-option-label="(item) => item.title"
11
+ :options="conditions"
12
+ >
13
+ <template #selected-option="{ option }">
14
+ <span class="sign-box"> {{option.title}}</span>
15
+ </template>
16
+ <template #option="{ option }">
17
+ {{option.title}}
18
+ </template>
19
+ </itf-select>
20
+ </div>
21
+ <div class="w-50">
22
+ <itf-text-field />
23
+ </div>
24
+ </div>
25
+
26
+ </template>
27
+ <style>
28
+ </style>
29
+ <script>
30
+ import { Component, Prop, Model, Vue, Watch } from 'vue-property-decorator';
31
+ import itfButton from '@itfin/components/src/components/button/Button.vue';
32
+ import itfTextField from '@itfin/components/src/components/text-field/TextField.vue';
33
+ import itfSelect from '@itfin/components/src/components/select/Select.vue';
34
+ import itfDropdown from '@itfin/components/src/components/dropdown/Dropdown.vue';
35
+ import itfIcon from '@itfin/components/src/components/icon/Icon.vue';
36
+
37
+ const conditions = [
38
+ { id: 'eq', title: 'equal', sign: '=' },
39
+ { id: 'notEq', title: 'not equal', sign: '≠' },
40
+ { id: 'contains', title: 'contains', sign: '∋' },
41
+ { id: 'noContains', title: 'not contains', sign: '∌' },
42
+ { id: 'startsWith', title: 'starts with', sign: 'A…' },
43
+ { id: 'endsWith', title: 'ends with', sign: '…Z' },
44
+ // { id: 'exists', title: 'exists', sign: '∃' }
45
+ ];
46
+
47
+ export default @Component({
48
+ name: 'itfFilterInput',
49
+ components: {
50
+ itfTextField,
51
+ itfSelect,
52
+ itfDropdown,
53
+ itfButton,
54
+ itfIcon
55
+ }
56
+ })
57
+ class FilterInput extends Vue {
58
+ @Prop() fields;
59
+
60
+ get conditions() {
61
+ return conditions;
62
+ }
63
+ }
64
+ </script>
@@ -0,0 +1,153 @@
1
+ <template>
2
+ <div tabindex="-1" class="b-panel" :class="{'b-panel__collapsed': collapsed, 'b-panel__active': !collapsed}">
3
+ <div v-if="collapsed" class="b-panel__expand">
4
+ <itf-button v-if="closeable" icon small class="b-panel__expand_button" @click="closePanel">
5
+ <itf-icon name="cross" />
6
+ </itf-button>
7
+ <a href="" class="b-panel__expand_text_container" @click.stop.prevent="expandPanel">
8
+ <itf-icon v-if="icon" :name="icon" class="mt-2" />
9
+
10
+ <div class="b-panel__expand_text" v-text="title"></div>
11
+ </a>
12
+ </div>
13
+ <div v-show="!collapsed" class="b-panel-header">
14
+ <div>
15
+ <slot name="title">
16
+ <div class="b-panel__title" v-text="title"></div>
17
+ </slot>
18
+ </div>
19
+ <div>
20
+ <slot name="buttons"></slot>
21
+ <itf-button v-if="expandable" icon small class="b-panel__expand_button" @click="fullsizePanel">
22
+ <itf-icon name="expand" />
23
+ </itf-button>
24
+ <itf-button v-if="closeable" icon small class="b-panel__expand_button" @click="closePanel">
25
+ <itf-icon name="cross" />
26
+ </itf-button>
27
+ </div>
28
+ </div>
29
+ <div v-show="!collapsed" class="b-panel-body">
30
+ <slot></slot>
31
+ </div>
32
+ </div>
33
+ </template>
34
+ <style lang="scss">
35
+ .b-panel {
36
+ --b-panel-bg: var(--bs-body-bg);
37
+ --b-panel-color: var(--bs-body-color);
38
+ --b-panel-box-shadow: 0px 2px 6px 0px rgba(21,23,25,.05);
39
+
40
+ margin-left: 8px;
41
+
42
+ display: flex;
43
+ flex-direction: column;
44
+ border-radius: 6px;
45
+ margin-bottom: 8px;
46
+ margin-right: 8px;
47
+ margin-top: 8px;
48
+ z-index: 0;
49
+ background: var(--b-panel-bg);
50
+ box-shadow: var(--b-panel-box-shadow);
51
+
52
+ &__collapsed {
53
+ flex-basis: 38px;
54
+ flex-grow: 0;
55
+ flex-shrink: 0;
56
+ min-width: 38px;
57
+ overflow: hidden;
58
+ position: relative;
59
+ }
60
+ &__active {
61
+ overflow-y: hidden;
62
+
63
+ flex-basis: 0;
64
+ flex-grow: 3;
65
+ flex-shrink: 0;
66
+ min-width: 490px;
67
+ }
68
+ &__expand {
69
+ align-items: center;
70
+ display: flex;
71
+ flex-direction: column;
72
+ height: 100%;
73
+ position: relative;
74
+ padding-top: 2px;
75
+ }
76
+ &__expand_text_container {
77
+ text-decoration: none;
78
+ align-items: center;
79
+ justify-content: start;
80
+ display: flex;
81
+ flex-basis: 0;
82
+ flex-direction: column;
83
+ flex-grow: 1;
84
+ flex-shrink: 0;
85
+ min-height: 0;
86
+ }
87
+ &__expand_text {
88
+ min-height: 0;
89
+ padding-bottom: 12px;
90
+ padding-top: 12px;
91
+ writing-mode: vertical-lr;
92
+ }
93
+ }
94
+ .b-panel-body {
95
+ overflow: auto;
96
+ position: relative;
97
+ z-index: 0;
98
+ align-items: stretch;
99
+ display: flex;
100
+ flex-direction: column;
101
+ height: 100%;
102
+ }
103
+ .b-panel-header {
104
+ align-items: center;
105
+ background: var(--b-panel-bg);
106
+ color: var(--b-panel-color);
107
+ display: flex;
108
+ gap: 8px;
109
+ justify-content: space-between;
110
+ min-height: 45px;
111
+ min-width: 0;
112
+ padding: 8px;
113
+ position: sticky;
114
+ top: 0;
115
+ z-index: 10;
116
+ }
117
+ </style>
118
+ <script>
119
+ import { Vue, Prop, Component } from 'vue-property-decorator';
120
+ import itfIcon from '@itfin/components/src/components/icon/Icon';
121
+ import itfButton from '@itfin/components/src/components/button/Button.vue';
122
+
123
+ export default @Component({
124
+ components: {
125
+ itfIcon,
126
+ itfButton
127
+ },
128
+ directives: {
129
+ },
130
+ filters: {
131
+ }
132
+ })
133
+ class Panel extends Vue {
134
+ @Prop() title;
135
+ @Prop() icon;
136
+ @Prop() payload;
137
+ @Prop(Boolean) collapsed;
138
+ @Prop(Boolean) closeable;
139
+ @Prop(Boolean) expandable;
140
+
141
+ expandPanel() {
142
+ this.$emit('expand');
143
+ }
144
+
145
+ fullsizePanel() {
146
+ this.$emit('fullsize');
147
+ }
148
+
149
+ closePanel() {
150
+ this.$emit('close');
151
+ }
152
+ }
153
+ </script>
@@ -0,0 +1,197 @@
1
+ <template>
2
+ <div class="b-panel-list-container">
3
+ <div class="b-panel-list">
4
+ <template v-for="(panel, n) of panelsStack">
5
+ <panel
6
+ :key="n"
7
+ :title="panel.title"
8
+ :icon="panel.icon"
9
+ :payload="panel.payload"
10
+ :expandable="panelsStack.length > 1"
11
+ :collapsed="panel.isCollapsed"
12
+ :closeable="panel.isCloseable"
13
+ @expand="expandPanel(panel)"
14
+ @fullsize="fullsizePanel(panel)"
15
+ @close="closePanel(panel)"
16
+ >
17
+ <slot
18
+ :name="panel.type"
19
+ :panel="panel"
20
+ :payload="panel.payload"
21
+ :multiple="isOpenMultiple"
22
+ :open="(title, icon, type, payload) => openPanel(title, icon, type, payload, n + 1)"
23
+ :close="() => closePanel(panel)"
24
+ :expand="() => expandPanel(panel)"
25
+ :fullsize="() => fullsizePanel(panel)">
26
+ </slot>
27
+ <template v-if="$scopedSlots[`${panel.type}.title`]" #title>
28
+ <slot
29
+ :name="`${panel.type}.title`"
30
+ :panel="panel"
31
+ :payload="panel.payload"
32
+ :multiple="isOpenMultiple"
33
+ :open="(title, icon, type, payload) => openPanel(title, icon, type, payload, n + 1)"
34
+ :close="() => closePanel(panel)"
35
+ :expand="() => expandPanel(panel)"
36
+ :fullsize="() => fullsizePanel(panel)">
37
+ </slot>
38
+ </template>
39
+ <template v-if="$scopedSlots[`${panel.type}.buttons`]" #buttons>
40
+ <slot
41
+ :name="`${panel.type}.buttons`"
42
+ :panel="panel"
43
+ :payload="panel.payload"
44
+ :multiple="isOpenMultiple"
45
+ :open="(title, icon, type, payload) => openPanel(title, icon, type, payload, n + 1)"
46
+ :close="() => closePanel(panel)"
47
+ :expand="() => expandPanel(panel)"
48
+ :fullsize="() => fullsizePanel(panel)">
49
+ </slot>
50
+ </template>
51
+ </panel>
52
+ </template>
53
+ </div>
54
+ </div>
55
+ </template>
56
+ <style>
57
+ .b-panel-list-container {
58
+ width: 100%;
59
+ height: 100%;
60
+ position: relative;
61
+ flex-grow: 1;
62
+ }
63
+ .b-panel-list {
64
+ align-items: stretch;
65
+ //background: var(--bs-body-bg);
66
+ bottom: 0;
67
+ display: flex;
68
+ height: 100%;
69
+ left: 0;
70
+ overflow-x: auto;
71
+ position: absolute;
72
+ right: 0;
73
+ top: 0;
74
+ }
75
+ </style>
76
+ <script lang="ts">
77
+ import { Vue, Component, Prop } from 'vue-property-decorator';
78
+ import Panel from '~/components/Panels/Panel.vue';
79
+
80
+ interface IPanel {
81
+ id: number;
82
+ title: string;
83
+ icon: string;
84
+ type: string;
85
+ payload: any;
86
+ isCollapsed: boolean;
87
+ isCloseable: boolean;
88
+ }
89
+
90
+ @Component({
91
+ components: {
92
+ Panel
93
+ },
94
+ directives: {
95
+ },
96
+ filters: {
97
+ }
98
+ })
99
+ export default class PanelList extends Vue {
100
+ @Prop() firstPanel: IPanel;
101
+
102
+ panelsStack:IPanel[] = [];
103
+
104
+ nextId:number = 0;
105
+
106
+ created() {
107
+ if (this.firstPanel) {
108
+ this.openPanel(this.firstPanel.title, this.firstPanel.icon, this.firstPanel.type, this.firstPanel.payload);
109
+ }
110
+ }
111
+
112
+ get isOpenMultiple() {
113
+ return this.panelsStack.filter(p => !p.isCollapsed).length > 1;
114
+ }
115
+
116
+ expandPanel(panel: IPanel) {
117
+ const newStack = [...this.panelsStack];
118
+ const index = newStack.findIndex(p => p.id === panel.id);
119
+ newStack[index].isCollapsed = false;
120
+ this.panelsStack = newStack;
121
+ this.ensureOnlyTwoOpenPanels(panel.id);
122
+ }
123
+
124
+ ensureOnlyTwoOpenPanels(keepOpenId: number) {
125
+ const newStack = [...this.panelsStack];
126
+ const openPanels = newStack.filter((panel) => !panel.isCollapsed);
127
+ if (openPanels.length > 1) {
128
+ const keepOpenIds = [keepOpenId];
129
+ const [panel1, panel2] = openPanels.filter((panel) => panel.id !== keepOpenId);
130
+ const indexKeep = newStack.findIndex(p => p.id === keepOpenId);
131
+ const index1 = newStack.findIndex(p => p === panel1);
132
+ const index2 = panel2 && newStack.findIndex(p => p === panel2);
133
+ if (index1 - 1 === indexKeep || index1 + 1 === indexKeep) {
134
+ keepOpenIds.push(panel1.id);
135
+ }
136
+ if (panel2 && (index2 - 1 === indexKeep || index2 + 1 === indexKeep)) {
137
+ keepOpenIds.push(panel2.id);
138
+ }
139
+ if (keepOpenIds.length === 1) {
140
+ if (newStack.length - 1 === indexKeep) {
141
+ keepOpenIds.push(newStack[indexKeep - 1].id);
142
+ } else {
143
+ keepOpenIds.push(newStack[indexKeep + 1].id);
144
+ }
145
+ }
146
+ for (const panel of newStack) {
147
+ panel.isCollapsed = !keepOpenIds.includes(panel.id);
148
+ }
149
+ }
150
+ this.panelsStack = newStack;
151
+ }
152
+
153
+ openPanel(title: string, icon: string, type: string, payload: any, openIndex?: number) {
154
+ const newPanel:IPanel = {
155
+ id: this.nextId++,
156
+ title,
157
+ icon,
158
+ type,
159
+ payload,
160
+ isCollapsed: false,
161
+ isCloseable: true
162
+ };
163
+ if (!this.panelsStack.length) {
164
+ newPanel.isCloseable = false;
165
+ }
166
+ let newStack = [...this.panelsStack];
167
+ if (openIndex) {
168
+ newStack = newStack.slice(0, openIndex);
169
+ }
170
+ newStack.push(newPanel);
171
+ this.panelsStack = newStack;
172
+ this.ensureOnlyTwoOpenPanels(newPanel.id);
173
+ }
174
+
175
+ closePanel(panel: IPanel) {
176
+ this.panelsStack = this.panelsStack.filter(p => p !== panel);
177
+ let openPanel = this.panelsStack.find(p => !p.isCollapsed);
178
+ if (!openPanel) {
179
+ openPanel = this.panelsStack[this.panelsStack.length - 1];
180
+ openPanel.isCollapsed = false;
181
+ }
182
+ const openPanelIndex = this.panelsStack.findIndex(p => p === openPanel);
183
+ if (openPanelIndex > 0) {
184
+ this.panelsStack[openPanelIndex - 1].isCollapsed = false;
185
+ }
186
+ this.ensureOnlyTwoOpenPanels(openPanel.id);
187
+ }
188
+
189
+ fullsizePanel(panel: IPanel) {
190
+ const newStack = [...this.panelsStack];
191
+ for (const p of newStack) {
192
+ p.isCollapsed = p !== panel;
193
+ }
194
+ this.panelsStack = newStack;
195
+ }
196
+ }
197
+ </script>
@@ -30,7 +30,7 @@
30
30
  <itf-checkbox :value="item[idProperty]" />
31
31
  </div>
32
32
  </div>
33
- <div accept-group="items" class="table-item-inner">
33
+ <div accept-group="items" class="table-item-inner" @click="$emit('row-click', item)">
34
34
  <template v-for="(column, k) in visibleAttributes">
35
35
  <div
36
36
  v-if="column.visible !== false"
@@ -51,7 +51,7 @@
51
51
  />
52
52
  <itf-textarea class="w-100" :rows="1" autogrow v-else-if="column.type === 'textarea'" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
53
53
  <itf-money-field class="w-100" currency-disabled :currency="currency" :currencies="currencies" v-else-if="column.type === 'money'" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
54
- <itf-select class="w-100" v-else-if="column.type === 'select'" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" :options="column.options"></itf-select>
54
+ <itf-select class="w-100" v-else-if="column.type === 'select'" :reduce="(item) => item.value" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" :options="column.options"></itf-select>
55
55
  </slot>
56
56
  </template>
57
57
  <div v-else class="h-100 align-items-center d-flex w-100">
@@ -257,7 +257,7 @@ class itfTableBody extends Vue {
257
257
  updateValue(item, value, index, column) {
258
258
  const newItem = { ...item };
259
259
  set(newItem, column.property, value);
260
- this.$emit('update', { index, item, value: newItem });
260
+ this.$emit('update', { index, item, value: newItem, column });
261
261
  }
262
262
  }
263
263
  </script>
@@ -1,78 +0,0 @@
1
- <template>
2
-
3
-
4
- <span class="b-filter-badge border rounded d-inline-flex ps-3 pe-1 gap-1 align-items-center">
5
- Status
6
-
7
- <itf-select
8
- class="mx-2"
9
- v-model="operator"
10
- simple
11
- :reduce="(item) => item.id"
12
- :get-option-label="(item) => item.title"
13
- :options="operators"
14
- >
15
- <template #selected-option="{ option }">
16
- <span class="sign-box"> {{option.sign || option.title}}</span>
17
- </template>
18
- <template #option="{ option }">
19
- <span class="sign-box" v-if="option.sign"> {{option.sign}}</span>
20
- {{option.title}}
21
- </template>
22
- <template #open-indicator><div></div></template>
23
- </itf-select>
24
-
25
- <div>
26
- <itf-select
27
- simple
28
- multiple
29
- :get-option-label="(item) => item.title"
30
- :options="[{ title:'text' }, { title:'text2' }]"
31
- >
32
- </itf-select>
33
- </div>
34
-
35
- <itf-button icon>
36
- <itf-icon name="close" />
37
- </itf-button>
38
- </span>
39
-
40
- </template>
41
- <style>
42
- .b-filter-badge {
43
- padding: 1px;
44
-
45
- .sign-box {
46
- height: 24px;
47
- text-align: center;
48
- line-height: 22px;
49
- width: 24px;
50
- border-radius: 0.25rem;
51
- background-color: var(--bs-tertiary-bg);
52
- }
53
- }
54
- </style>
55
- <script>
56
- import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
57
- import {getOperatorsByType} from "./constants";
58
- import itfButton from '../button/Button.vue';
59
- import itfSelect from '../select/Select.vue';
60
- import itfIcon from '../icon/Icon.vue';
61
-
62
-
63
- export default @Component({
64
- name: 'itfFilterBadge',
65
- components: {
66
- itfButton,
67
- itfSelect,
68
- itfIcon
69
- }
70
- })
71
- class itfFilterBadge extends Vue {
72
- @Prop({ type: String, required: true }) type;
73
-
74
- get operators() {
75
- return getOperatorsByType(this.type);
76
- }
77
- }
78
- </script>
@@ -1,105 +0,0 @@
1
- <template>
2
-
3
-
4
- <div class="d-flex align-items-center">
5
- <itf-text-field
6
- style="width: 250px;"
7
- prepend-icon="search"
8
- placeholder="Search"
9
- @change="onQueryChanged"
10
- />
11
-
12
- <itf-dropdown class="ms-3" :button-options="{ secondary: true }">
13
- <template #button>
14
- <itf-icon name="flip_view" class="me-1" />
15
- Group
16
- </template>
17
-
18
- <div v-for="(filter, n) of filters" :key="n" class="dropdown-item">
19
- <slot name="dropdown-item" :item="filter">{{filter.text}}</slot>
20
- </div>
21
- </itf-dropdown>
22
-
23
- <template v-if="filters && filters.length">
24
- <itf-dropdown class="mx-3" :button-options="{ secondary: true }">
25
- <template #button>
26
- <itf-icon name="filter" class="me-1" />
27
- Filter
28
- </template>
29
-
30
- <div v-for="(filter, n) of filters" :key="n" class="dropdown-item">
31
- <slot name="dropdown-item" :item="filter">{{filter.text}}</slot>
32
- </div>
33
- </itf-dropdown>
34
-
35
- <div class="flex-grow-1">
36
- <itf-filter-badge type="string" />
37
- </div>
38
- </template>
39
- </div>
40
-
41
- </template>
42
- <style>
43
- </style>
44
- <script>
45
- import { Component, Prop, Model, Vue, Watch } from 'vue-property-decorator';
46
- import itfButton from '../button/Button.vue';
47
- import itfTextField from '../text-field/TextField.vue';
48
- import itfDropdown from '../dropdown/Dropdown.vue';
49
- import itfIcon from '../icon/Icon.vue';
50
- import itfFilterBadge from './FilterBadge.vue';
51
-
52
- export default @Component({
53
- name: 'itfFilterControl',
54
- components: {
55
- itfFilterBadge,
56
- itfTextField,
57
- itfDropdown,
58
- itfButton,
59
- itfIcon
60
- }
61
- })
62
- class itfFilterControl extends Vue {
63
- @Model('input') value;
64
- @Prop({ type: String, default: 'query' }) queryField;
65
- @Prop({ type: Array, default: () => ([]) }) filters;
66
- @Prop(Boolean) urlSync;
67
-
68
- mounted() {
69
- if (this.urlSync) {
70
- if (window.itfinUrlWatcher) {
71
- throw new Error('urlSync component already exists');
72
- }
73
- window.itfinUrlWatcher = this;
74
- }
75
- }
76
-
77
- @Watch('$route')
78
- onRouteChange() {
79
- const newValue = { ...(this.value || {}) };
80
- const query = this.$route.query;
81
- if (query) {
82
- Object.keys(query).forEach((key) => {
83
- newValue[key] = query[key];
84
- });
85
- }
86
- this.$emit('input', newValue);
87
- }
88
-
89
- beforeDestroy() {
90
- if (this.urlSync) {
91
- window.itfinUrlWatcher = null;
92
- }
93
- }
94
-
95
- onQueryChanged(value) {
96
- const newValue = { ...(this.value || {}) };
97
- newValue[this.queryField] = value;
98
- if (this.urlSync) {
99
- this.$router.push({ ...this.$route, query: newValue });
100
- } else {
101
- this.$emit('input', newValue);
102
- }
103
- }
104
- }
105
- </script>
@@ -1,129 +0,0 @@
1
- <template>
2
- <div class="form-group and-or-rule d-flex">
3
- <div class="col-3">
4
- <select class="form-control input-sm" v-model="key">
5
- <option v-for="option in options.keys" :value="option.id">
6
- {{option.name}}
7
- </option>
8
- </select>
9
- </div>
10
-
11
- <div style="width: 150px" class="px-2">
12
- <select class="form-control input-sm input-filter" v-model="operator">
13
- <option v-for="option in operators" :value="option.id">
14
- {{option.title}}
15
- </option>
16
- </select>
17
- </div>
18
-
19
- <div class="col-3">
20
- <input type="text" class="form-control input-sm" v-model="value" placeholder="">
21
- </div>
22
- <div class="col-3">
23
- <itf-button small @click.prevent="deleteSelf()">
24
- <itf-icon name="close" />
25
- </itf-button>
26
- </div>
27
- </div>
28
- </template>
29
- <style>
30
- .input-filter {
31
- background: var(--bs-primary-bg-subtle);
32
- }
33
- .and-or-rule {
34
- position: relative;
35
- margin-left: 15px !important;
36
- padding-left: 0;
37
- }
38
-
39
- .and-or-rule:before,
40
- .and-or-rule:after {
41
- content: '';
42
- position: absolute;
43
- left: -1px;
44
- width: 16px;
45
- height: calc(50% + 15px);
46
- border-color: #c0c5e2;
47
- border-style: solid;
48
- }
49
-
50
- .and-or-rule:before {
51
- top: -15px;
52
- border-width: 0 0 2px 2px;
53
- }
54
-
55
- .and-or-rule:after {
56
- top: 50%;
57
- border-width: 0 0 0 2px;
58
- }
59
-
60
- .and-or-rule:last-child:after {
61
- border: none;
62
- }
63
- </style>
64
- <script>
65
- import { Component, Prop, Watch, Vue } from "vue-property-decorator";
66
- import itfButton from '../button/Button.vue';
67
- import itfIcon from '../icon/Icon.vue';
68
-
69
- const operations = [
70
- {
71
- type: 'string',
72
- operators: [
73
- { id: 'eq', title: 'equal' },
74
- { id: 'notEq', title: 'not equal' },
75
- { id: 'contains', title: 'contains' },
76
- { id: 'noContains', title: 'not contains' },
77
- { id: 'startsWith', title: 'starts with' },
78
- { id: 'endsWith', title: 'ends with' }
79
- ]
80
- }
81
- ];
82
-
83
- export default @Component({
84
- name: 'itfRule',
85
- components: { itfButton, itfIcon }
86
- })
87
- class itfRule extends Vue {
88
- @Prop() options;
89
- @Prop() type;
90
-
91
- key = -99;
92
-
93
- operator = -99;
94
-
95
- value = '';
96
-
97
- get operators() {
98
- return operations.find(op => op.type === this.type)?.operators || [];
99
- }
100
-
101
- @Watch('options.keys.options')
102
- onOptionUpdated() {
103
- this.key = -99;
104
- }
105
-
106
- @Watch('options.conditions.options')
107
- onOption2Updated() {
108
- this.condition = -99;
109
- }
110
-
111
- deleteSelf () {
112
- this.$emit('delete-rule');
113
- }
114
-
115
- queryFormStatus () {
116
- return {
117
- 'key': this.key,
118
- 'operator': this.operator,
119
- 'value': this.value
120
- }
121
- }
122
-
123
- fillRuleStatus (data) {
124
- this.key = data.key;
125
- this.operator = data.operator;
126
- this.value = data.value;
127
- }
128
- }
129
- </script>
@@ -1,205 +0,0 @@
1
- <template>
2
- <div>
3
- <itf-dropdown>
4
- <template #button>
5
- <itf-icon name="filter" />
6
- Filter
7
- </template>
8
-
9
- <div class="dropdown-item" @click="addGroup">Add group</div>
10
- <div class="dropdown-item" @click="addRule">Add condition</div>
11
- </itf-dropdown>
12
- <div class="form-group and-or-top col-12">
13
- <div class="col-5" style="padding: 0">
14
- <button class="btn btn-xs btn-purple-outline btn-radius"
15
- :class=" isAnd ? 'btn-purple-outline-focus' : '' " @click.prevent="clickAnd">
16
- &nbsp;And&nbsp;
17
- </button>
18
- <button class="btn btn-xs btn-purple-outline btn-radius"
19
- :class=" !isAnd ? 'btn-purple-outline-focus' : '' " @click.prevent="clickOr">
20
- &nbsp;Or&nbsp;
21
- </button>
22
- </div>
23
-
24
- <div class="col-7 btn-and-or">
25
- <itf-button v-if="!first" class="btn btn-xs btn-purple pull-right" @click.prevent="deleteSelf()">
26
- <i class="fa fa-fw fa-close"></i>
27
- </itf-button>
28
- </div>
29
- </div>
30
-
31
- <rule
32
- v-for="(rule, index) in rules"
33
- ref="rules"
34
- :options="options"
35
- :key="rule"
36
- type="string"
37
- @delete-rule="deleteRule(index)">
38
- </rule>
39
-
40
- <itf-rule-group
41
- class="and-or-offset col-11"
42
- v-for="(group, index) in groups" ref="groups"
43
- :options="options" :key="group" @delete-group="deleteGroup(index)">
44
- </itf-rule-group>
45
-
46
- </div>
47
- </template>
48
- <style>
49
- .and-or-template {
50
- position: relative;
51
- margin-bottom: 20px;
52
- }
53
-
54
- .and-or-template:before,
55
- .and-or-template:after {
56
- content: '';
57
- position: absolute;
58
- left: -17px;
59
- width: 16px;
60
- height: calc(50% + 18px);
61
- border-color: #c0c5e2;
62
- border-style: solid;
63
- }
64
-
65
- .and-or-template:before {
66
- top: -18px;
67
- border-width: 0 0 2px 2px;
68
- }
69
-
70
- .and-or-template:after {
71
- top: 50%;
72
- border-width: 0 0 0 2px;
73
- }
74
-
75
- .and-or-first:before,
76
- .and-or-first:after,
77
- .and-or-template:last-child:after {
78
- border: none;
79
- }
80
-
81
- .and-or-top,
82
- .btn-and-or {
83
- padding: 0;
84
- }
85
-
86
- .btn-and-or button {
87
- margin-left: 4px;
88
- }
89
-
90
- .and-or-offset {
91
- margin-left: 30px;
92
- }
93
- </style>
94
- <script>
95
- import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
96
- import itfButton from '../button/Button.vue';
97
- import itfIcon from '../icon/Icon.vue';
98
- import itfDropdown from '../dropdown/Dropdown.vue';
99
- import Rule from './Rule'
100
-
101
- export default @Component({
102
- name: 'itfRuleGroup',
103
- components: {
104
- itfIcon,
105
- itfButton,
106
- itfDropdown,
107
- Rule
108
- }
109
- })
110
- class itfRule extends Vue {
111
- @Prop({ type: Object, required: true }) options;
112
- @Prop({ type: Boolean, default: false }) first;
113
-
114
- isAnd = true;
115
- groups = [];
116
- rules = [];
117
-
118
- created () {
119
- this.addRule();
120
- }
121
-
122
- clickAnd () {
123
- this.isAnd = true;
124
- }
125
-
126
- clickOr () {
127
- this.isAnd = false;
128
- }
129
-
130
- addRule () {
131
- var id = this.generateId();
132
- this.rules.push(id);
133
- }
134
-
135
- addGroup () {
136
- var id = this.generateId();
137
- this.groups.push(id);
138
- }
139
-
140
- deleteSelf () {
141
- this.$emit('delete-group');
142
- }
143
-
144
- deleteRule (index) {
145
- this.rules.splice(index, 1);
146
- }
147
-
148
- deleteGroup (index) {
149
- this.groups.splice(index, 1);
150
- }
151
-
152
- queryFormStatus () {
153
- var query = {};
154
- var rules = this.$refs.rules || {};
155
- var groups = this.$refs.groups || {};
156
- var i, j;
157
-
158
- query['condition'] = this.isAnd ? 'AND' : 'OR';
159
- query['rules'] = [];
160
- for(i = 0; i < rules.length; i++){
161
- query.rules.push(rules[i].queryFormStatus ());
162
- }
163
- for(j = 0; j < groups.length; j++){
164
- query.rules[query.rules.length] = groups[j].queryFormStatus();
165
- }
166
- return query;
167
- }
168
-
169
- fillFormStatus (data) {
170
- var i, len;
171
- var group = this;
172
- group.rules = [];
173
- group.groups = [];
174
- if(data){
175
- group.isAnd = /and/i.test(data.condition);
176
- len = data.rules.length;
177
- for(i = 0; i < len; i++){
178
- if(data.rules[i].condition){
179
- group.groups.push(group.generateId());
180
- (function (i, index) {
181
- group.$nextTick(function () {
182
- group.$refs.groups[index].fillFormStatus(data.rules[i]);
183
- });
184
- })(i, group.groups.length - 1);
185
- }
186
- else {
187
- group.rules.push(group.generateId());
188
- (function (i, index) {
189
- group.$nextTick(function () {
190
- group.$refs.rules[index].fillRuleStatus(data.rules[i]);
191
- });
192
- })(i, group.rules.length - 1);
193
- }
194
- }
195
- }
196
- }
197
-
198
- generateId () {
199
- return 'xxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
200
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
201
- return v.toString(16);
202
- });
203
- }
204
- }
205
- </script>