@operato/form 8.0.0-alpha.6 → 8.0.0-beta.1

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 (44) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/dist/src/filters/ox-filters-form-base.js +2 -1
  3. package/dist/src/filters/ox-filters-form-base.js.map +1 -1
  4. package/dist/tsconfig.tsbuildinfo +1 -1
  5. package/package.json +15 -15
  6. package/themes/calendar-theme.css +3 -1
  7. package/.editorconfig +0 -29
  8. package/.storybook/main.js +0 -3
  9. package/.storybook/preview.js +0 -52
  10. package/.storybook/server.mjs +0 -8
  11. package/src/components/ox-custom-input.ts +0 -102
  12. package/src/components/ox-custom-select.ts +0 -91
  13. package/src/components/ox-file-uploader.ts +0 -164
  14. package/src/components/ox-form-master.ts +0 -246
  15. package/src/components/ox-form-paginator.ts +0 -60
  16. package/src/components/ox-search-form-paginator.ts +0 -46
  17. package/src/components/ox-search-form.ts +0 -467
  18. package/src/filters/filter-checkbox.ts +0 -34
  19. package/src/filters/filter-input-barcode.ts +0 -35
  20. package/src/filters/filter-input.ts +0 -30
  21. package/src/filters/filter-range-date.ts +0 -81
  22. package/src/filters/filter-range-number.ts +0 -64
  23. package/src/filters/filter-select.ts +0 -56
  24. package/src/filters/filter-styles.ts +0 -127
  25. package/src/filters/index.ts +0 -10
  26. package/src/filters/ox-filter-personalizer.ts +0 -188
  27. package/src/filters/ox-filters-form-base.ts +0 -436
  28. package/src/filters/registry.ts +0 -54
  29. package/src/index.ts +0 -4
  30. package/src/styles/index.ts +0 -3
  31. package/src/styles/multi-column-form-styles.ts +0 -192
  32. package/src/styles/search-form-styles.ts +0 -160
  33. package/src/styles/single-column-form-styles.ts +0 -114
  34. package/src/types.ts +0 -106
  35. package/stories/ox-filters-form-barcode.stories.ts +0 -140
  36. package/stories/ox-filters-form-base-bounded.stories.ts +0 -208
  37. package/stories/ox-filters-form-base-change-handler.stories.ts +0 -220
  38. package/stories/ox-filters-form-base-page-preference.stories.ts +0 -177
  39. package/stories/ox-filters-form-base.stories.ts +0 -179
  40. package/stories/ox-search-form-change-handler.stories.ts +0 -253
  41. package/stories/ox-search-form.stories.ts +0 -258
  42. package/tsconfig.json +0 -24
  43. package/web-dev-server.config.mjs +0 -27
  44. package/web-test-runner.config.mjs +0 -41
@@ -1,436 +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
- this.value = await this.getQueryFilters()
104
-
105
- keepGoing &&
106
- this.dispatchEvent(
107
- new CustomEvent('filters-change', {
108
- bubbles: true,
109
- composed: true,
110
- detail: {
111
- filters: this.value,
112
- from: 'filters-form'
113
- }
114
- })
115
- )
116
- })
117
- }
118
-
119
- buildDefaultValue(operator: FilterOperator, defaultValue: any) {
120
- if (defaultValue === undefined) {
121
- return
122
- }
123
- if (operator == 'between') {
124
- return defaultValue instanceof Array && (defaultValue as Array<any>).map(v => getDefaultValue(v, this))
125
- } else {
126
- return getDefaultValue(defaultValue, this)
127
- }
128
- }
129
-
130
- render(): TemplateResult {
131
- return html`
132
- <form
133
- class="md-typescale-body-medium-prominent"
134
- @submit=${(e: Event) => {
135
- e.preventDefault()
136
- }}
137
- >
138
- ${(this.personalFilters || [])
139
- .filter(filter => !filter.hidden)
140
- .map((filter: FilterConfig) => {
141
- const { name, label, type, operator, readonly, value: defaultValue } = filter
142
- const idx = operator === 'between' ? 1 : 0
143
- const renderer = getFilterRenderer(
144
- operator === 'like' || operator === 'i_like' || operator === 'i_nlike' || operator === 'nlike'
145
- ? 'text'
146
- : type
147
- )[idx]
148
-
149
- const value =
150
- this.value?.find(filter => filter.name == name)?.value ?? this.buildDefaultValue(operator!, defaultValue)
151
-
152
- if (!renderer) {
153
- return nothing
154
- }
155
-
156
- return type === 'search'
157
- ? html`<ox-input-search
158
- name="search"
159
- .value=${value}
160
- ?autofocus=${this.autofocus}
161
- ?readonly=${readonly}
162
- ></ox-input-search>`
163
- : type === 'boolean' || type === 'checkbox'
164
- ? renderer(filter, value, this)
165
- : type !== 'select'
166
- ? html`
167
- <label filter-title ?between=${operator == 'between'}
168
- ><span>${label}</span> ${renderer(filter, value, this)}
169
- </label>
170
- `
171
- : this.renderSelect(filter, value, renderer)
172
- })}
173
- </form>
174
-
175
- <slot name="setting"></slot>
176
- `
177
- }
178
-
179
- renderSelect(filter: FilterConfig, value: any, renderer: FilterSelectRenderer) {
180
- const { name, label, operator, readonly, value: defaultValue } = filter
181
-
182
- return operator === 'in'
183
- ? html`
184
- <ox-select
185
- name=${name}
186
- placeholder=${label}
187
- .value=${value ?? defaultValue}
188
- @change=${(e: CustomEvent) =>
189
- e.target?.dispatchEvent(
190
- new CustomEvent('filter-change', {
191
- detail: {
192
- name,
193
- operator,
194
- value: e.detail
195
- }
196
- })
197
- )}
198
- ?readonly=${readonly}
199
- >
200
- <ox-popup-list multiple attr-selected="checked" with-search>
201
- ${renderer(filter, value, this)}
202
- </ox-popup-list>
203
- </ox-select>
204
- `
205
- : html`
206
- <ox-select
207
- name=${name}
208
- placeholder=${label}
209
- .value=${value ?? defaultValue}
210
- @change=${(e: CustomEvent) =>
211
- e.target?.dispatchEvent(
212
- new CustomEvent('filter-change', {
213
- detail: {
214
- name,
215
- operator,
216
- value: e.detail
217
- }
218
- })
219
- )}
220
- ?readonly=${readonly}
221
- >
222
- <ox-popup-list with-search> ${renderer(filter, value, this)} </ox-popup-list>
223
- </ox-select>
224
- `
225
- }
226
-
227
- async updated(changes: PropertyValues<this>) {
228
- if (changes.has('urlParamsSensitive')) {
229
- if (this.urlParamsSensitive) {
230
- this._popstateEventHandler = () => {
231
- const queryString = window.location.search
232
- const urlParams = new URLSearchParams(queryString)
233
- var filters = urlParams.get('filters')
234
-
235
- if (filters) {
236
- try {
237
- this.value = json5.parse(filters)
238
- this.dispatchEvent(
239
- new CustomEvent('filters-change', {
240
- bubbles: true,
241
- composed: true,
242
- detail: {
243
- filters: json5.parse(filters),
244
- from: 'url-parameter'
245
- }
246
- })
247
- )
248
- } catch (e) {
249
- console.error(`invalid filters params on URL query string : ${e}`)
250
- }
251
- }
252
- }
253
-
254
- //@ts-ignore
255
- this._popstateEventHandler() // call for the first time
256
-
257
- window.addEventListener('popstate', this._popstateEventHandler)
258
-
259
- this._filtersChangeHandler = (e: Event) => {
260
- const urlParams = new URLSearchParams(window.location.search)
261
- const { filters, from } = (e as CustomEvent).detail || {}
262
-
263
- if (from === 'url-parameter') {
264
- return
265
- }
266
-
267
- if (filters.length > 0) {
268
- urlParams.set('filters', json5.stringify(filters))
269
- } else {
270
- urlParams.delete('filters')
271
- }
272
- const queryString = String(urlParams)
273
- const url = `${window.location.pathname}${queryString ? `?${queryString}` : ''}`
274
-
275
- history.pushState({}, document.title, url)
276
- }
277
-
278
- this.addEventListener('filters-change', this._filtersChangeHandler)
279
- } else {
280
- if (this._popstateEventHandler) {
281
- window.removeEventListener('popstate', this._popstateEventHandler)
282
- }
283
- if (this._filtersChangeHandler) {
284
- this.removeEventListener('popstate', this._filtersChangeHandler)
285
- }
286
- }
287
- }
288
-
289
- if (changes.has('personalConfigProvider') && this.personalConfigProvider) {
290
- this.personalConfig = await this.personalConfigProvider.load()
291
- } else if (changes.has('filters') || changes.has('personalConfig')) {
292
- this.applyUpdatedConfiguration()
293
- }
294
- }
295
-
296
- async applyUpdatedConfiguration() {
297
- if (!this.filters) {
298
- return
299
- }
300
-
301
- if (!this.personalConfig) {
302
- this.personalFilters = [...this.filters]
303
- } else {
304
- const { filters: personalFilters } = this.personalConfig
305
-
306
- if (personalFilters) {
307
- const xfilters = this.filters.map((filter: Partial<FilterConfig>) => {
308
- const personalFilter = personalFilters.find(pFilter => pFilter.name == filter.name)
309
- return personalFilter ? { ...filter, ...personalFilter } : filter
310
- })
311
-
312
- function reorderList(a: Partial<FilterConfig>[], b: Partial<FilterConfig>[]): Partial<FilterConfig>[] {
313
- // 결과 배열 초기화, a 배열 길이만큼 undefined로 채움
314
- const result = new Array(a.length)
315
-
316
- // b 배열에 없는 아이템은 원래 위치로 채움
317
- a.forEach((item, index) => {
318
- if (!item.name || !b.find(bi => bi.name == item.name)) {
319
- result[index] = item
320
- }
321
- })
322
-
323
- b.forEach(item => {
324
- const ai = a.find(ai => ai.name == item.name)
325
- if (ai) {
326
- result[result.findIndex(slot => slot === undefined)] = ai
327
- }
328
- })
329
-
330
- return result
331
- }
332
-
333
- // 배열 재정렬 실행
334
- this.personalFilters = reorderList(xfilters as any, personalFilters as any) as FilterConfig[]
335
- }
336
- }
337
-
338
- this.autoUpdateTargetsOnChange = {}
339
-
340
- this.personalFilters
341
- ?.filter(filter => filter.boundTo && filter.boundTo.length > 0)
342
- .map(({ name, boundTo }) => {
343
- boundTo!.forEach(to => {
344
- const origin = this.autoUpdateTargetsOnChange[to] || []
345
- if (name && !origin.includes(name)) {
346
- this.autoUpdateTargetsOnChange[to] = [...origin, name]
347
- }
348
- })
349
- })
350
-
351
- if (!this.urlParamsSensitive) {
352
- this.value = await this.getQueryFilters()
353
- }
354
- }
355
-
356
- getFieldByName(name: string) {
357
- return this.renderRoot.querySelector(`[name=${name}]`)
358
- }
359
-
360
- async getQueryFilters(): Promise<FilterValue[]> {
361
- const formData = new FormData(await this.form)
362
-
363
- var filters = (this.personalFilters || [])
364
- .map((filter: FilterConfig) => {
365
- const { name, type, operator } = filter
366
-
367
- var value = formData.getAll(name)
368
- if (value.length == 0) {
369
- return
370
- }
371
-
372
- if (-1 === value.findIndex(v => v !== '')) {
373
- return
374
- }
375
-
376
- const filterValue = value.map(v => {
377
- const value = v.toString()
378
-
379
- /* TODO registry에서 타입별로 parsing 방법을 지정할 수 있도록 해야한다. */
380
- switch (type) {
381
- case 'integer':
382
- case 'float':
383
- case 'number':
384
- case 'progress':
385
- case 'checkbox':
386
- case 'boolean':
387
- return !value ? undefined : JSON.parse(value)
388
- default:
389
- return value
390
- }
391
- })
392
-
393
- return {
394
- name,
395
- operator,
396
- value: filterValue.length === 1 ? filterValue[0] : filterValue
397
- }
398
- })
399
- .filter(result => result !== undefined) as FilterValue[]
400
-
401
- return filters
402
- }
403
-
404
- reset(value?: string[] | { [field: string]: any }) {
405
- this.form
406
- .then((form: HTMLFormElement) => {
407
- if (value) {
408
- if (value instanceof Array) {
409
- value
410
- .filter(item => typeof item == 'string')
411
- .forEach(item => {
412
- const field = this.getFieldByName(item)
413
- if (field) {
414
- ;(field as HTMLInputElement).value = ''
415
- }
416
- })
417
- } else {
418
- const keys = Object.keys(value)
419
- keys
420
- .filter(key => typeof key == 'string')
421
- .forEach(key => {
422
- const field = this.getFieldByName(key)
423
- if (field) {
424
- ;(field as HTMLInputElement).value = value[key]
425
- }
426
- })
427
- }
428
- } else {
429
- form.reset()
430
- }
431
- })
432
- .catch((error: any) => {
433
- console.error('Error resetting the form:', error)
434
- })
435
- }
436
- }
@@ -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
- `