@operato/form 8.0.0-beta.0 → 8.0.0-beta.2

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +7 -7
  3. package/.editorconfig +0 -29
  4. package/.storybook/main.js +0 -3
  5. package/.storybook/preview.js +0 -52
  6. package/.storybook/server.mjs +0 -8
  7. package/demo/index.html +0 -110
  8. package/src/components/ox-custom-input.ts +0 -102
  9. package/src/components/ox-custom-select.ts +0 -91
  10. package/src/components/ox-file-uploader.ts +0 -164
  11. package/src/components/ox-form-master.ts +0 -246
  12. package/src/components/ox-form-paginator.ts +0 -60
  13. package/src/components/ox-search-form-paginator.ts +0 -46
  14. package/src/components/ox-search-form.ts +0 -467
  15. package/src/filters/filter-checkbox.ts +0 -34
  16. package/src/filters/filter-input-barcode.ts +0 -35
  17. package/src/filters/filter-input.ts +0 -30
  18. package/src/filters/filter-range-date.ts +0 -81
  19. package/src/filters/filter-range-number.ts +0 -64
  20. package/src/filters/filter-select.ts +0 -56
  21. package/src/filters/filter-styles.ts +0 -127
  22. package/src/filters/index.ts +0 -10
  23. package/src/filters/ox-filter-personalizer.ts +0 -188
  24. package/src/filters/ox-filters-form-base.ts +0 -437
  25. package/src/filters/registry.ts +0 -54
  26. package/src/index.ts +0 -4
  27. package/src/styles/index.ts +0 -3
  28. package/src/styles/multi-column-form-styles.ts +0 -192
  29. package/src/styles/search-form-styles.ts +0 -160
  30. package/src/styles/single-column-form-styles.ts +0 -114
  31. package/src/types.ts +0 -106
  32. package/stories/ox-filters-form-barcode.stories.ts +0 -140
  33. package/stories/ox-filters-form-base-bounded.stories.ts +0 -208
  34. package/stories/ox-filters-form-base-change-handler.stories.ts +0 -220
  35. package/stories/ox-filters-form-base-page-preference.stories.ts +0 -177
  36. package/stories/ox-filters-form-base.stories.ts +0 -179
  37. package/stories/ox-search-form-change-handler.stories.ts +0 -253
  38. package/stories/ox-search-form.stories.ts +0 -258
  39. package/tsconfig.json +0 -24
  40. package/web-dev-server.config.mjs +0 -27
  41. package/web-test-runner.config.mjs +0 -41
@@ -1,437 +0,0 @@
1
- import '@operato/input/ox-checkbox.js'
2
- import '@operato/input/ox-select.js'
3
- import '@operato/popup/ox-popup-list.js'
4
- import '@operato/input/ox-input-search.js'
5
-
6
- import json5 from 'json5'
7
- import { css, html, LitElement, nothing, PropertyValues, TemplateResult } from 'lit'
8
- import { customElement, property, queryAsync, state } from 'lit/decorators.js'
9
- import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles'
10
-
11
- import { getDefaultValue } from '@operato/time-calculator'
12
- import { PagePreferenceProvider } from '@operato/p13n'
13
-
14
- import { FilterStyles } from './filter-styles.js'
15
- import { getFilterRenderer } from './registry.js'
16
- import { FilterConfig, FilterOperator, FilterSelectRenderer, FilterValue, PersonalFilterPreference } from '../types.js'
17
-
18
- export type QueryFilterRangeValue = [from: number, to: number]
19
-
20
- @customElement('ox-filters-form-base')
21
- export class OxFiltersFormBase extends LitElement {
22
- static styles = [
23
- MDTypeScaleStyles,
24
- FilterStyles,
25
- css`
26
- :host {
27
- display: flex;
28
- padding: var(--spacing-small);
29
- }
30
-
31
- form {
32
- flex: 1;
33
-
34
- display: flex;
35
- flex-flow: row wrap;
36
- gap: var(--spacing-medium) var(--spacing-large);
37
- }
38
-
39
- form > * {
40
- display: flex;
41
- align-items: center;
42
- gap: var(--spacing-small, 4px);
43
- }
44
-
45
- label span {
46
- display: block;
47
- }
48
-
49
- @media only screen and (max-width: 460px) {
50
- form {
51
- flex-direction: column;
52
- flex-flow: column;
53
- }
54
- }
55
- `
56
- ]
57
-
58
- @property({ type: Array }) value: FilterValue[] = []
59
- @property({ type: Array }) filters: FilterConfig[] = []
60
- @property({ type: Boolean, attribute: 'url-params-sensitive' }) urlParamsSensitive?: boolean
61
- @property({ type: Boolean, attribute: 'autofocus' }) autofocus: boolean = true
62
- @property({ type: Object }) personalConfigProvider?: PagePreferenceProvider
63
-
64
- @state() personalConfig?: PersonalFilterPreference
65
- @state() personalFilters?: FilterConfig[]
66
-
67
- @queryAsync('form') form!: HTMLFormElement
68
-
69
- private _popstateEventHandler?: EventListener
70
- private _filtersChangeHandler?: EventListener
71
- private autoUpdateTargetsOnChange: { [name: string]: string[] } = {}
72
-
73
- connectedCallback(): void {
74
- super.connectedCallback()
75
-
76
- this.renderRoot.addEventListener('change', async (e: Event) => {
77
- const { target, detail: value } = e as CustomEvent
78
- const name = (target as HTMLInputElement).name
79
- const {
80
- label,
81
- type,
82
- operator,
83
- value: defaultValue,
84
- onchange
85
- } = (this.personalFilters || []).find(filter => filter.name == name) || {}
86
-
87
- if (this.autoUpdateTargetsOnChange[name]) {
88
- /*
89
- 일단은 심플하게, boundTo로 연결된 필터값이 바뀌면, 폼 전체를 update하도록 함.
90
- */
91
- ;(this.autoUpdateTargetsOnChange[name] || []).forEach(name => {
92
- const target = this.renderRoot.querySelector(`[name='${name}']`)
93
- if (target) {
94
- ;(target as HTMLInputElement).value = ''
95
- }
96
- })
97
-
98
- this.requestUpdate()
99
- }
100
-
101
- const keepGoing = onchange ? await onchange.call(null, value ?? (target as HTMLInputElement).value, this) : true
102
-
103
- // filters-form과 동일하게 value 세팅 X (셋팅후 다시 셋팅하여 랜더링을 다시하면서 값이 깨짐)
104
- // this.value = await this.getQueryFilters()
105
-
106
- keepGoing &&
107
- this.dispatchEvent(
108
- new CustomEvent('filters-change', {
109
- bubbles: true,
110
- composed: true,
111
- detail: {
112
- filters: this.value,
113
- from: 'filters-form'
114
- }
115
- })
116
- )
117
- })
118
- }
119
-
120
- buildDefaultValue(operator: FilterOperator, defaultValue: any) {
121
- if (defaultValue === undefined) {
122
- return
123
- }
124
- if (operator == 'between') {
125
- return defaultValue instanceof Array && (defaultValue as Array<any>).map(v => getDefaultValue(v, this))
126
- } else {
127
- return getDefaultValue(defaultValue, this)
128
- }
129
- }
130
-
131
- render(): TemplateResult {
132
- return html`
133
- <form
134
- class="md-typescale-body-medium-prominent"
135
- @submit=${(e: Event) => {
136
- e.preventDefault()
137
- }}
138
- >
139
- ${(this.personalFilters || [])
140
- .filter(filter => !filter.hidden)
141
- .map((filter: FilterConfig) => {
142
- const { name, label, type, operator, readonly, value: defaultValue } = filter
143
- const idx = operator === 'between' ? 1 : 0
144
- const renderer = getFilterRenderer(
145
- operator === 'like' || operator === 'i_like' || operator === 'i_nlike' || operator === 'nlike'
146
- ? 'text'
147
- : type
148
- )[idx]
149
-
150
- const value =
151
- this.value?.find(filter => filter.name == name)?.value ?? this.buildDefaultValue(operator!, defaultValue)
152
-
153
- if (!renderer) {
154
- return nothing
155
- }
156
-
157
- return type === 'search'
158
- ? html`<ox-input-search
159
- name="search"
160
- .value=${value}
161
- ?autofocus=${this.autofocus}
162
- ?readonly=${readonly}
163
- ></ox-input-search>`
164
- : type === 'boolean' || type === 'checkbox'
165
- ? renderer(filter, value, this)
166
- : type !== 'select'
167
- ? html`
168
- <label filter-title ?between=${operator == 'between'}
169
- ><span>${label}</span> ${renderer(filter, value, this)}
170
- </label>
171
- `
172
- : this.renderSelect(filter, value, renderer)
173
- })}
174
- </form>
175
-
176
- <slot name="setting"></slot>
177
- `
178
- }
179
-
180
- renderSelect(filter: FilterConfig, value: any, renderer: FilterSelectRenderer) {
181
- const { name, label, operator, readonly, value: defaultValue } = filter
182
-
183
- return operator === 'in'
184
- ? html`
185
- <ox-select
186
- name=${name}
187
- placeholder=${label}
188
- .value=${value ?? defaultValue}
189
- @change=${(e: CustomEvent) =>
190
- e.target?.dispatchEvent(
191
- new CustomEvent('filter-change', {
192
- detail: {
193
- name,
194
- operator,
195
- value: e.detail
196
- }
197
- })
198
- )}
199
- ?readonly=${readonly}
200
- >
201
- <ox-popup-list multiple attr-selected="checked" with-search>
202
- ${renderer(filter, value, this)}
203
- </ox-popup-list>
204
- </ox-select>
205
- `
206
- : html`
207
- <ox-select
208
- name=${name}
209
- placeholder=${label}
210
- .value=${value ?? defaultValue}
211
- @change=${(e: CustomEvent) =>
212
- e.target?.dispatchEvent(
213
- new CustomEvent('filter-change', {
214
- detail: {
215
- name,
216
- operator,
217
- value: e.detail
218
- }
219
- })
220
- )}
221
- ?readonly=${readonly}
222
- >
223
- <ox-popup-list with-search> ${renderer(filter, value, this)} </ox-popup-list>
224
- </ox-select>
225
- `
226
- }
227
-
228
- async updated(changes: PropertyValues<this>) {
229
- if (changes.has('urlParamsSensitive')) {
230
- if (this.urlParamsSensitive) {
231
- this._popstateEventHandler = () => {
232
- const queryString = window.location.search
233
- const urlParams = new URLSearchParams(queryString)
234
- var filters = urlParams.get('filters')
235
-
236
- if (filters) {
237
- try {
238
- this.value = json5.parse(filters)
239
- this.dispatchEvent(
240
- new CustomEvent('filters-change', {
241
- bubbles: true,
242
- composed: true,
243
- detail: {
244
- filters: json5.parse(filters),
245
- from: 'url-parameter'
246
- }
247
- })
248
- )
249
- } catch (e) {
250
- console.error(`invalid filters params on URL query string : ${e}`)
251
- }
252
- }
253
- }
254
-
255
- //@ts-ignore
256
- this._popstateEventHandler() // call for the first time
257
-
258
- window.addEventListener('popstate', this._popstateEventHandler)
259
-
260
- this._filtersChangeHandler = (e: Event) => {
261
- const urlParams = new URLSearchParams(window.location.search)
262
- const { filters, from } = (e as CustomEvent).detail || {}
263
-
264
- if (from === 'url-parameter') {
265
- return
266
- }
267
-
268
- if (filters.length > 0) {
269
- urlParams.set('filters', json5.stringify(filters))
270
- } else {
271
- urlParams.delete('filters')
272
- }
273
- const queryString = String(urlParams)
274
- const url = `${window.location.pathname}${queryString ? `?${queryString}` : ''}`
275
-
276
- history.pushState({}, document.title, url)
277
- }
278
-
279
- this.addEventListener('filters-change', this._filtersChangeHandler)
280
- } else {
281
- if (this._popstateEventHandler) {
282
- window.removeEventListener('popstate', this._popstateEventHandler)
283
- }
284
- if (this._filtersChangeHandler) {
285
- this.removeEventListener('popstate', this._filtersChangeHandler)
286
- }
287
- }
288
- }
289
-
290
- if (changes.has('personalConfigProvider') && this.personalConfigProvider) {
291
- this.personalConfig = await this.personalConfigProvider.load()
292
- } else if (changes.has('filters') || changes.has('personalConfig')) {
293
- this.applyUpdatedConfiguration()
294
- }
295
- }
296
-
297
- async applyUpdatedConfiguration() {
298
- if (!this.filters) {
299
- return
300
- }
301
-
302
- if (!this.personalConfig) {
303
- this.personalFilters = [...this.filters]
304
- } else {
305
- const { filters: personalFilters } = this.personalConfig
306
-
307
- if (personalFilters) {
308
- const xfilters = this.filters.map((filter: Partial<FilterConfig>) => {
309
- const personalFilter = personalFilters.find(pFilter => pFilter.name == filter.name)
310
- return personalFilter ? { ...filter, ...personalFilter } : filter
311
- })
312
-
313
- function reorderList(a: Partial<FilterConfig>[], b: Partial<FilterConfig>[]): Partial<FilterConfig>[] {
314
- // 결과 배열 초기화, a 배열 길이만큼 undefined로 채움
315
- const result = new Array(a.length)
316
-
317
- // b 배열에 없는 아이템은 원래 위치로 채움
318
- a.forEach((item, index) => {
319
- if (!item.name || !b.find(bi => bi.name == item.name)) {
320
- result[index] = item
321
- }
322
- })
323
-
324
- b.forEach(item => {
325
- const ai = a.find(ai => ai.name == item.name)
326
- if (ai) {
327
- result[result.findIndex(slot => slot === undefined)] = ai
328
- }
329
- })
330
-
331
- return result
332
- }
333
-
334
- // 배열 재정렬 실행
335
- this.personalFilters = reorderList(xfilters as any, personalFilters as any) as FilterConfig[]
336
- }
337
- }
338
-
339
- this.autoUpdateTargetsOnChange = {}
340
-
341
- this.personalFilters
342
- ?.filter(filter => filter.boundTo && filter.boundTo.length > 0)
343
- .map(({ name, boundTo }) => {
344
- boundTo!.forEach(to => {
345
- const origin = this.autoUpdateTargetsOnChange[to] || []
346
- if (name && !origin.includes(name)) {
347
- this.autoUpdateTargetsOnChange[to] = [...origin, name]
348
- }
349
- })
350
- })
351
-
352
- if (!this.urlParamsSensitive) {
353
- this.value = await this.getQueryFilters()
354
- }
355
- }
356
-
357
- getFieldByName(name: string) {
358
- return this.renderRoot.querySelector(`[name=${name}]`)
359
- }
360
-
361
- async getQueryFilters(): Promise<FilterValue[]> {
362
- const formData = new FormData(await this.form)
363
-
364
- var filters = (this.personalFilters || [])
365
- .map((filter: FilterConfig) => {
366
- const { name, type, operator } = filter
367
-
368
- var value = formData.getAll(name)
369
- if (value.length == 0) {
370
- return
371
- }
372
-
373
- if (-1 === value.findIndex(v => v !== '')) {
374
- return
375
- }
376
-
377
- const filterValue = value.map(v => {
378
- const value = v.toString()
379
-
380
- /* TODO registry에서 타입별로 parsing 방법을 지정할 수 있도록 해야한다. */
381
- switch (type) {
382
- case 'integer':
383
- case 'float':
384
- case 'number':
385
- case 'progress':
386
- case 'checkbox':
387
- case 'boolean':
388
- return !value ? undefined : JSON.parse(value)
389
- default:
390
- return value
391
- }
392
- })
393
-
394
- return {
395
- name,
396
- operator,
397
- value: filterValue.length === 1 ? filterValue[0] : filterValue
398
- }
399
- })
400
- .filter(result => result !== undefined) as FilterValue[]
401
-
402
- return filters
403
- }
404
-
405
- reset(value?: string[] | { [field: string]: any }) {
406
- this.form
407
- .then((form: HTMLFormElement) => {
408
- if (value) {
409
- if (value instanceof Array) {
410
- value
411
- .filter(item => typeof item == 'string')
412
- .forEach(item => {
413
- const field = this.getFieldByName(item)
414
- if (field) {
415
- ;(field as HTMLInputElement).value = ''
416
- }
417
- })
418
- } else {
419
- const keys = Object.keys(value)
420
- keys
421
- .filter(key => typeof key == 'string')
422
- .forEach(key => {
423
- const field = this.getFieldByName(key)
424
- if (field) {
425
- ;(field as HTMLInputElement).value = value[key]
426
- }
427
- })
428
- }
429
- } else {
430
- form.reset()
431
- }
432
- })
433
- .catch((error: any) => {
434
- console.error('Error resetting the form:', error)
435
- })
436
- }
437
- }
@@ -1,54 +0,0 @@
1
- import { FilterCheckbox } from './filter-checkbox.js'
2
- import { FilterInputBarcode } from './filter-input-barcode.js'
3
- import { FilterInput } from './filter-input.js'
4
- import { FilterRangeDate } from './filter-range-date.js'
5
- import { FilterRangeNumber } from './filter-range-number.js'
6
- import { FilterSelect } from './filter-select.js'
7
- import { FilterSelectRenderer } from '../types.js'
8
-
9
- var RENDERERS: {
10
- [name: string]: FilterSelectRenderer[]
11
- } = {
12
- string: [FilterInput],
13
- text: [FilterInput],
14
- email: [FilterInput],
15
- tel: [FilterInput],
16
- integer: [FilterInput, FilterRangeNumber],
17
- float: [FilterInput, FilterRangeNumber],
18
- number: [FilterInput, FilterRangeNumber],
19
- select: [FilterSelect],
20
- boolean: [FilterCheckbox],
21
- checkbox: [FilterCheckbox],
22
- month: [FilterInput, FilterRangeDate],
23
- week: [FilterInput, FilterRangeDate],
24
- date: [FilterInput, FilterRangeDate],
25
- time: [FilterInput, FilterRangeDate],
26
- datetime: [FilterInput, FilterRangeDate],
27
- color: [FilterInput],
28
- progress: [FilterInput, FilterRangeNumber],
29
- link: [FilterInput],
30
- image: [FilterInput],
31
- json5: [FilterInput],
32
- scanner: [FilterInputBarcode],
33
- barcode: [FilterInputBarcode]
34
- }
35
-
36
- export function registerFilterRenderer(type: string, renderer: FilterSelectRenderer[]) {
37
- RENDERERS[type] = renderer
38
- }
39
-
40
- export function unregisterFilterRenderer(type: string): void {
41
- delete RENDERERS[type]
42
- }
43
-
44
- export function getFilterRenderers(): { [name: string]: FilterSelectRenderer[] } {
45
- return { ...RENDERERS }
46
- }
47
-
48
- export function getFilterRenderer(type: string): FilterSelectRenderer[] {
49
- if (typeof type == 'function') {
50
- return type
51
- }
52
-
53
- return RENDERERS[type || 'text'] || [FilterInput]
54
- }
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from './styles'
2
- export * from './components/ox-search-form'
3
- export * from './components/ox-file-uploader'
4
- export * from './filters'
@@ -1,3 +0,0 @@
1
- export * from './single-column-form-styles'
2
- export * from './multi-column-form-styles'
3
- export * from './search-form-styles'
@@ -1,192 +0,0 @@
1
- import { css } from 'lit'
2
-
3
- export const MultiColumnFormStyles = css`
4
- :host {
5
- overflow: auto;
6
- padding: var(--form-container-padding);
7
- }
8
-
9
- .multi-column-form {
10
- display: grid;
11
- grid-template-columns: repeat(24, 1fr);
12
- grid-gap: var(--form-grid-gap);
13
- grid-auto-rows: minmax(24px, auto);
14
- max-width: var(--form-multi-column-max-width);
15
- margin: var(--form-margin);
16
- }
17
- .multi-column-form fieldset {
18
- display: contents;
19
- }
20
- .multi-column-form legend {
21
- grid-column: span 24;
22
- text-transform: capitalize;
23
-
24
- padding: var(--legend-padding);
25
- font: var(--legend-font);
26
- color: var(--legend-text-color, var(--md-sys-color-on-surface-variant));
27
- border-bottom: var(--legend-border-bottom);
28
- }
29
-
30
- .multi-column-form label {
31
- grid-column: span 3;
32
- text-align: right;
33
- text-transform: capitalize;
34
- align-self: center;
35
-
36
- color: var(--label-color, var(--md-sys-color-on-surface));
37
- font: var(--label-font);
38
- }
39
-
40
- .multi-column-form input,
41
- .multi-column-form table,
42
- .multi-column-form select,
43
- .multi-column-form textarea,
44
- .multi-column-form [custom-input] {
45
- grid-column: span 9;
46
-
47
- border: var(--input-field-border);
48
- border-radius: var(--input-field-border-radius);
49
- padding: var(--input-field-padding);
50
- font: var(--input-field-font);
51
- max-width: 85%;
52
- }
53
- .multi-column-form select {
54
- min-height: 25px;
55
- max-width: calc(85% + 18px);
56
- }
57
-
58
- .multi-column-form input[type='checkbox'],
59
- .multi-column-form input[type='radio'] {
60
- justify-self: end;
61
- align-self: center;
62
- grid-column: span 3 / auto;
63
- position: relative;
64
- left: 17px;
65
- }
66
-
67
- .multi-column-form input[type='checkbox'] + label,
68
- .multi-column-form input[type='radio'] + label {
69
- padding-left: 17px;
70
- text-align: left;
71
- align-self: center;
72
- grid-column: span 9 / auto;
73
-
74
- font: var(--form-sublabel-font);
75
- color: var(--form-sublabel-color, var(--md-sys-color-secondary));
76
- }
77
- .multi-column-form [options-wrap] {
78
- grid-column: span 21 / auto;
79
- }
80
-
81
- input:focus {
82
- outline: none;
83
- border: 1px solid var(--focus-background-color, var(--md-sys-color-surface-variant));
84
- }
85
- input[type='checkbox'] {
86
- margin: 0;
87
- }
88
-
89
- @media screen and (max-width: 460px) {
90
- .multi-column-form {
91
- max-width: 100%;
92
- grid-template-columns: repeat(12, 1fr);
93
- grid-gap: 5px;
94
- }
95
- .multi-column-form legend {
96
- grid-column: span 12;
97
- }
98
- .multi-column-form label {
99
- grid-column: span 4 / auto;
100
- text-align: left;
101
- text-overflow: ellipsis;
102
- white-space: nowrap;
103
- overflow: hidden;
104
- }
105
- .multi-column-form input,
106
- .multi-column-form table,
107
- .multi-column-form select,
108
- .multi-column-form textarea,
109
- .multi-column-form [custom-input] {
110
- grid-column: span 8 / auto;
111
- max-width: initial;
112
- padding: var(--padding-default, var(--spacing-medium));
113
- font: normal 15px var(--theme-font);
114
- }
115
- .multi-column-form input[type='checkbox'],
116
- .multi-column-form input[type='radio'] {
117
- justify-self: left;
118
- align-self: center;
119
- grid-column: span 1 / auto;
120
- left: 0;
121
- zoom: 1.4;
122
- }
123
-
124
- .multi-column-form input[type='checkbox'] + label,
125
- .multi-column-form input[type='radio'] + label {
126
- grid-column: span 11 / auto;
127
- align-self: center;
128
- position: relative;
129
- left: -25px;
130
- }
131
- .multi-column-form [options-wrap] {
132
- grid-column: span 12 / auto;
133
- display: grid;
134
- grid-gap: var(--padding-default, var(--spacing-small)) var(--spacing-small, var(--spacing-small));
135
- grid-template-columns: repeat(12, 1fr);
136
- }
137
- .multi-column-form [options-wrap] input[type='checkbox'] + label,
138
- .multi-column-form [options-wrap] input[type='radio'] + label {
139
- grid-column: span 5 / auto;
140
- align-self: left;
141
- left: -4%;
142
- margin-right: 0;
143
- }
144
- }
145
- @media (min-width: 481px) and (max-width: 1024px) {
146
- .multi-column-form select {
147
- max-width: calc(85% + 10px);
148
- }
149
- }
150
- @media screen and (min-width: 1201px) and (max-width: 2000px) {
151
- .multi-column-form {
152
- grid-template-columns: repeat(36, 1fr);
153
- }
154
- .multi-column-form legend {
155
- grid-column: span 36;
156
- }
157
- .multi-column-form input,
158
- .multi-column-form table,
159
- .multi-column-form select,
160
- .multi-column-form textarea,
161
- .multi-column-form [custom-input] {
162
- max-width: 90%;
163
- }
164
- .multi-column-form select {
165
- max-width: calc(90% + 18px);
166
- }
167
- .multi-column-form [options-wrap] {
168
- grid-column: span 33 / auto;
169
- }
170
- }
171
- @media screen and (min-width: 2001px) {
172
- .multi-column-form {
173
- grid-template-columns: repeat(48, 1fr);
174
- }
175
- .multi-column-form legend {
176
- grid-column: span 48;
177
- }
178
- .multi-column-form input,
179
- .multi-column-form table,
180
- .multi-column-form select,
181
- .multi-column-form textarea,
182
- .multi-column-form [custom-input] {
183
- max-width: 90%;
184
- }
185
- .multi-column-form select {
186
- max-width: calc(90% + 18px);
187
- }
188
- .multi-column-form [options-wrap] {
189
- grid-column: span 45 / auto;
190
- }
191
- }
192
- `