@crombolo/backoffice-host 0.10.0 → 0.12.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.
@@ -10,27 +10,45 @@
10
10
  * back with the strict parser before keeping it, so a refusal here is the same
11
11
  * refusal a hand-written file would get.
12
12
  */
13
+ // For its side effect: registers `umb-input-document`, which the shell loads lazily.
14
+ import '@umbraco-cms/backoffice/document'
13
15
  import { css, html, nothing, repeat } from '@umbraco-cms/backoffice/external/lit'
14
16
  import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
15
- import { deleteForm, listForms, readForm, saveForm } from './forms-client.js'
17
+ import {
18
+ deleteForm,
19
+ listForms,
20
+ listFormTypes,
21
+ previewChoices,
22
+ readForm,
23
+ saveForm,
24
+ } from './forms-client.js'
25
+ import {
26
+ addGroup,
27
+ addPage,
28
+ groupHasFields,
29
+ layoutRows,
30
+ moveField,
31
+ moveGroup,
32
+ movePage,
33
+ nudgeField,
34
+ pageHasFields,
35
+ removeGroup,
36
+ removePage,
37
+ setColumns,
38
+ setGroupCaption,
39
+ setPageCaption,
40
+ withLayout,
41
+ } from './forms-layout.js'
16
42
 
17
- const FIELD_TYPES = [
18
- 'shortAnswer',
19
- 'longAnswer',
20
- 'email',
21
- 'number',
22
- 'date',
23
- 'checkbox',
24
- 'dropdown',
25
- 'singleChoice',
26
- 'multipleChoice',
27
- 'fileUpload',
28
- 'dataConsent',
29
- 'titleAndDescription',
30
- 'richText',
31
- 'hidden',
43
+ /** What a preview depends on, so it survives a layout change that copies the field. */
44
+ const sourceKey = (source) => `${source.kind}|${source.parent}`
45
+
46
+ /** Where a choice field's options come from, as an editor would put it. */
47
+ const CHOICE_FROM = [
48
+ { kind: '', label: 'Options typed here' },
49
+ { kind: 'content', label: "A page's child pages" },
50
+ { kind: 'dictionary', label: "A dictionary item's children" },
32
51
  ]
33
- const CHOICE_TYPES = new Set(['dropdown', 'singleChoice', 'multipleChoice'])
34
52
 
35
53
  export default class CromboloFormsOverviewElement extends UmbLitElement {
36
54
  static properties = {
@@ -39,6 +57,8 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
39
57
  _problems: { state: true },
40
58
  _notice: { state: true },
41
59
  _busy: { state: true },
60
+ _choices: { state: true },
61
+ _types: { state: true },
42
62
  }
43
63
 
44
64
  constructor() {
@@ -48,6 +68,10 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
48
68
  this._problems = []
49
69
  this._notice = ''
50
70
  this._busy = false
71
+ /** What each source currently offers, by field; shown under the field. */
72
+ this._choices = new Map()
73
+ /** Field types by name, as the server has them: built in and from bundles. */
74
+ this._types = new Map()
51
75
  }
52
76
 
53
77
  connectedCallback() {
@@ -57,7 +81,9 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
57
81
 
58
82
  async #load() {
59
83
  try {
60
- this._forms = await listForms()
84
+ const [forms, types] = await Promise.all([listForms(), listFormTypes()])
85
+ this._forms = forms
86
+ this._types = new Map(types.fields.map((info) => [info.type, info]))
61
87
  } catch (error) {
62
88
  this._forms = []
63
89
  this._notice = error.message
@@ -68,7 +94,161 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
68
94
  this._problems = []
69
95
  this._notice = ''
70
96
  const detail = await readForm(key)
71
- this._editing = structuredClone(detail.definition)
97
+ this._editing = withLayout(detail.definition)
98
+ this._choices = new Map()
99
+ for (const field of this.#fields) if (field.source) this.#preview(field)
100
+ }
101
+
102
+ async #preview(field) {
103
+ const source = field.source
104
+ if (!source?.parent) {
105
+ this.#touch()
106
+ return
107
+ }
108
+ try {
109
+ const result = await previewChoices(source)
110
+ this._choices.set(sourceKey(source), result.ok ? result.body.options : [])
111
+ } catch {
112
+ this._choices.set(sourceKey(source), [])
113
+ }
114
+ this.#touch()
115
+ }
116
+
117
+ /** Clears what the old type kept, so a switched field carries no settings it no longer takes. */
118
+ #changeType(field, type) {
119
+ const before = this._types.get(field.type)
120
+ for (const setting of before?.builtIn ? before.settings : []) delete field[setting.key]
121
+ delete field.settings
122
+ field.type = type
123
+ if (!this._types.get(type)?.choices) {
124
+ field.values = []
125
+ delete field.source
126
+ }
127
+ }
128
+
129
+ /**
130
+ * One input per setting the type declares. A built-in type keeps them on the
131
+ * field; a bundle's type keeps them in the field's own settings.
132
+ */
133
+ #settingsCell(field) {
134
+ const info = this._types.get(field.type)
135
+ if (!info || info.settings.length === 0) return nothing
136
+ const read = (key) => (info.builtIn ? field[key] : field.settings?.[key])
137
+ const write = (key, value) => {
138
+ if (info.builtIn) {
139
+ if (value === undefined) delete field[key]
140
+ else field[key] = value
141
+ } else {
142
+ field.settings = { ...(field.settings ?? {}) }
143
+ if (value === undefined) delete field.settings[key]
144
+ else field.settings[key] = value
145
+ if (Object.keys(field.settings).length === 0) delete field.settings
146
+ }
147
+ this.#touch()
148
+ }
149
+ return html`<div class="settings">
150
+ ${info.settings.map((setting) => {
151
+ const value = read(setting.key)
152
+ const label = `${setting.label}${setting.required ? ' *' : ''}`
153
+ if (setting.kind === 'boolean')
154
+ return html`<uui-toggle
155
+ label=${label}
156
+ title=${setting.description ?? ''}
157
+ .checked=${value === true}
158
+ @change=${(event) => write(setting.key, event.target.checked || undefined)}
159
+ ></uui-toggle>`
160
+ return html`<label>
161
+ <span>${label}</span>
162
+ <uui-input
163
+ title=${setting.description ?? ''}
164
+ type=${setting.kind === 'number' ? 'number' : 'text'}
165
+ .value=${value === undefined ? '' : setting.kind === 'list' ? value.join(', ') : String(value)}
166
+ @change=${(event) => {
167
+ const raw = event.target.value.trim()
168
+ if (raw === '') return write(setting.key, undefined)
169
+ if (setting.kind === 'number') return write(setting.key, Number(raw))
170
+ if (setting.kind === 'list')
171
+ return write(
172
+ setting.key,
173
+ raw
174
+ .split(',')
175
+ .map((item) => item.trim())
176
+ .filter(Boolean),
177
+ )
178
+ write(setting.key, raw)
179
+ }}
180
+ ></uui-input>
181
+ </label>`
182
+ })}
183
+ </div>`
184
+ }
185
+
186
+ #choicesCell(field) {
187
+ const kind = field.source?.kind ?? ''
188
+ const offered = field.source?.parent ? this._choices.get(sourceKey(field.source)) : undefined
189
+ const list =
190
+ kind === ''
191
+ ? html`<uui-input
192
+ label="Options"
193
+ placeholder="One, Two, Three"
194
+ .value=${(field.values ?? []).join(', ')}
195
+ @change=${(event) => {
196
+ field.values = event.target.value
197
+ .split(',')
198
+ .map((value) => value.trim())
199
+ .filter(Boolean)
200
+ this.#touch()
201
+ }}
202
+ ></uui-input>`
203
+ : kind === 'content'
204
+ ? html`<umb-input-document
205
+ max="1"
206
+ .selection=${field.source.parent ? [field.source.parent] : []}
207
+ @change=${(event) => {
208
+ field.source = { kind, parent: event.target.selection?.[0] ?? '' }
209
+ this.#preview(field)
210
+ }}
211
+ ></umb-input-document>`
212
+ : html`<uui-input
213
+ label="Dictionary item"
214
+ placeholder="The name of the parent item"
215
+ .value=${field.source.parent ?? ''}
216
+ @change=${(event) => {
217
+ field.source = { kind, parent: event.target.value.trim() }
218
+ this.#preview(field)
219
+ }}
220
+ ></uui-input>`
221
+ return html`
222
+ <select
223
+ aria-label="Where the options come from"
224
+ @change=${(event) => {
225
+ const chosen = event.target.value
226
+ if (chosen === '') delete field.source
227
+ else {
228
+ field.source = { kind: chosen, parent: '' }
229
+ field.values = []
230
+ }
231
+ this.#touch()
232
+ }}
233
+ >
234
+ ${CHOICE_FROM.map(
235
+ (from) =>
236
+ html`<option value=${from.kind} ?selected=${from.kind === kind}>${from.label}</option>`,
237
+ )}
238
+ </select>
239
+ ${list}
240
+ ${
241
+ kind && offered
242
+ ? html`<p class="offered">
243
+ ${
244
+ offered.length > 0
245
+ ? `Offers now: ${offered.map((option) => option.label).join(', ')}`
246
+ : 'Offers nothing yet.'
247
+ }
248
+ </p>`
249
+ : nothing
250
+ }
251
+ `
72
252
  }
73
253
 
74
254
  #new() {
@@ -86,8 +266,216 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
86
266
  }
87
267
  }
88
268
 
269
+ /** Every field, in order: for the previews, which a layout change does not touch. */
89
270
  get #fields() {
90
- return this._editing?.pages?.[0]?.groups?.[0]?.fields ?? []
271
+ return (this._editing?.pages ?? []).flatMap((page) =>
272
+ page.groups.flatMap((group) => group.fields),
273
+ )
274
+ }
275
+
276
+ /** Applies a layout operation from forms-layout.js to the form being edited. */
277
+ #apply(operation, ...args) {
278
+ this._editing = operation(this._editing, ...args)
279
+ }
280
+
281
+ #page(page, p) {
282
+ const pages = this._editing.pages
283
+ return html`<section class="page" aria-label=${page.caption || `Page ${p + 1}`}>
284
+ <div class="bar">
285
+ <uui-input
286
+ label="Page title"
287
+ placeholder=${`Page ${p + 1}`}
288
+ .value=${page.caption ?? ''}
289
+ @change=${(event) => this.#apply(setPageCaption, p, event.target.value)}
290
+ ></uui-input>
291
+ <uui-button compact look="secondary" label="Move page up" ?disabled=${p === 0}
292
+ @click=${() => this.#apply(movePage, p, p - 1)}>↑</uui-button>
293
+ <uui-button compact look="secondary" label="Move page down" ?disabled=${p === pages.length - 1}
294
+ @click=${() => this.#apply(movePage, p, p + 1)}>↓</uui-button>
295
+ <uui-button compact look="secondary" color="danger" label="Remove page" ?disabled=${pages.length === 1}
296
+ @click=${() => {
297
+ if (pageHasFields(page) && !confirm('Remove this page and the fields on it?')) return
298
+ this.#apply(removePage, p)
299
+ }}></uui-button>
300
+ </div>
301
+ ${page.groups.map((group, g) => this.#group(group, p, g, page.groups.length))}
302
+ <uui-button look="secondary" label="Add a group" @click=${() => this.#apply(addGroup, p)}></uui-button>
303
+ </section>`
304
+ }
305
+
306
+ #group(group, p, g, count) {
307
+ return html`<div
308
+ class="group"
309
+ @dragover=${(event) => event.preventDefault()}
310
+ @drop=${(event) => this.#drop(event, { page: p, group: g, index: group.fields.length })}
311
+ >
312
+ <div class="bar">
313
+ <uui-input
314
+ label="Group title"
315
+ placeholder="Untitled group"
316
+ .value=${group.caption ?? ''}
317
+ @change=${(event) => this.#apply(setGroupCaption, p, g, event.target.value)}
318
+ ></uui-input>
319
+ <label class="columns">
320
+ Columns
321
+ <select @change=${(event) => this.#apply(setColumns, p, g, Number(event.target.value))}>
322
+ ${[1, 2, 3, 4].map((n) => html`<option value=${n} ?selected=${n === group.columns}>${n}</option>`)}
323
+ </select>
324
+ </label>
325
+ <uui-button compact look="secondary" label="Move group up" ?disabled=${g === 0}
326
+ @click=${() => this.#apply(moveGroup, p, g, g - 1)}>↑</uui-button>
327
+ <uui-button compact look="secondary" label="Move group down" ?disabled=${g === count - 1}
328
+ @click=${() => this.#apply(moveGroup, p, g, g + 1)}>↓</uui-button>
329
+ <uui-button compact look="secondary" color="danger" label="Remove group" ?disabled=${count === 1}
330
+ @click=${() => {
331
+ if (groupHasFields(group) && !confirm('Remove this group and its fields?')) return
332
+ this.#apply(removeGroup, p, g)
333
+ }}></uui-button>
334
+ </div>
335
+ ${
336
+ group.fields.length > 0
337
+ ? html`<div class="preview" style=${`grid-template-columns: repeat(${group.columns}, 1fr)`} aria-hidden="true">
338
+ ${layoutRows(group)
339
+ .flat()
340
+ .map(
341
+ (field) => html`<span>${field.caption || field.alias || 'Untitled field'}</span>`,
342
+ )}
343
+ </div>`
344
+ : nothing
345
+ }
346
+ ${this.#fieldsTable(group, p, g)}
347
+ <uui-button
348
+ look="secondary"
349
+ label="Add a field"
350
+ @click=${() => {
351
+ group.fields.push({
352
+ alias: '',
353
+ type: 'shortAnswer',
354
+ caption: '',
355
+ mandatory: false,
356
+ sensitive: false,
357
+ values: [],
358
+ accept: [],
359
+ })
360
+ this.#touch()
361
+ }}
362
+ ></uui-button>
363
+ </div>`
364
+ }
365
+
366
+ #drop(event, to) {
367
+ event.preventDefault()
368
+ event.stopPropagation()
369
+ const from = JSON.parse(event.dataTransfer?.getData('application/x-crombolo-field') || 'null')
370
+ if (from) this.#apply(moveField, from, to)
371
+ }
372
+
373
+ #fieldsTable(group, p, g) {
374
+ return html`<uui-table>
375
+ <uui-table-head>
376
+ <uui-table-head-cell>Order</uui-table-head-cell>
377
+ <uui-table-head-cell>Caption</uui-table-head-cell>
378
+ <uui-table-head-cell>Alias</uui-table-head-cell>
379
+ <uui-table-head-cell>Type</uui-table-head-cell>
380
+ <uui-table-head-cell>Choices</uui-table-head-cell>
381
+ <uui-table-head-cell>Settings</uui-table-head-cell>
382
+ <uui-table-head-cell>Required</uui-table-head-cell>
383
+ <uui-table-head-cell>Sensitive</uui-table-head-cell>
384
+ <uui-table-head-cell></uui-table-head-cell>
385
+ </uui-table-head>
386
+ ${repeat(
387
+ group.fields,
388
+ (_field, index) => index,
389
+ (field, index) => html`
390
+ <uui-table-row
391
+ draggable="true"
392
+ @dragstart=${(event) =>
393
+ event.dataTransfer?.setData(
394
+ 'application/x-crombolo-field',
395
+ JSON.stringify({ page: p, group: g, index }),
396
+ )}
397
+ @dragover=${(event) => event.preventDefault()}
398
+ @drop=${(event) => this.#drop(event, { page: p, group: g, index })}
399
+ >
400
+ <uui-table-cell>
401
+ <uui-button compact look="secondary" label="Move field up"
402
+ @click=${() => this.#apply(nudgeField, { page: p, group: g, index }, -1)}>↑</uui-button>
403
+ <uui-button compact look="secondary" label="Move field down"
404
+ @click=${() => this.#apply(nudgeField, { page: p, group: g, index }, 1)}>↓</uui-button>
405
+ </uui-table-cell>
406
+ <uui-table-cell>
407
+ <uui-input
408
+ label="Caption"
409
+ .value=${field.caption ?? ''}
410
+ @change=${(event) => {
411
+ field.caption = event.target.value
412
+ this.#touch()
413
+ }}
414
+ ></uui-input>
415
+ </uui-table-cell>
416
+ <uui-table-cell>
417
+ <uui-input
418
+ label="Alias"
419
+ .value=${field.alias ?? ''}
420
+ @change=${(event) => {
421
+ field.alias = event.target.value
422
+ this.#touch()
423
+ }}
424
+ ></uui-input>
425
+ </uui-table-cell>
426
+ <uui-table-cell>
427
+ <select
428
+ aria-label="Type"
429
+ @change=${(event) => {
430
+ this.#changeType(field, event.target.value)
431
+ this.#touch()
432
+ }}
433
+ >
434
+ ${[...this._types.values()].map(
435
+ (info) =>
436
+ html`<option value=${info.type} ?selected=${info.type === field.type}>${info.label}</option>`,
437
+ )}
438
+ </select>
439
+ </uui-table-cell>
440
+ <uui-table-cell>
441
+ ${this._types.get(field.type)?.choices ? this.#choicesCell(field) : nothing}
442
+ </uui-table-cell>
443
+ <uui-table-cell>${this.#settingsCell(field)}</uui-table-cell>
444
+ <uui-table-cell>
445
+ <uui-toggle
446
+ label="Required"
447
+ .checked=${field.mandatory === true}
448
+ @change=${(event) => {
449
+ field.mandatory = event.target.checked
450
+ this.#touch()
451
+ }}
452
+ ></uui-toggle>
453
+ </uui-table-cell>
454
+ <uui-table-cell>
455
+ <uui-toggle
456
+ label="Sensitive"
457
+ .checked=${field.sensitive === true}
458
+ @change=${(event) => {
459
+ field.sensitive = event.target.checked
460
+ this.#touch()
461
+ }}
462
+ ></uui-toggle>
463
+ </uui-table-cell>
464
+ <uui-table-cell>
465
+ <uui-button
466
+ look="secondary"
467
+ color="danger"
468
+ label="Remove"
469
+ @click=${() => {
470
+ group.fields.splice(index, 1)
471
+ this.#touch()
472
+ }}
473
+ ></uui-button>
474
+ </uui-table-cell>
475
+ </uui-table-row>
476
+ `,
477
+ )}
478
+ </uui-table>`
91
479
  }
92
480
 
93
481
  #touch() {
@@ -222,122 +610,29 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
222
610
  }}
223
611
  ></uui-toggle>
224
612
  </uui-form-layout-item>
613
+ ${
614
+ form.pages.length > 1
615
+ ? html`<uui-form-layout-item>
616
+ <uui-label slot="label">Show one page at a time</uui-label>
617
+ <span slot="description">Each page is checked before the next. Without JavaScript the pages show together.</span>
618
+ <uui-toggle
619
+ .checked=${form.stepPages === true}
620
+ @change=${(event) => {
621
+ if (event.target.checked) form.stepPages = true
622
+ else delete form.stepPages
623
+ this.#touch()
624
+ }}
625
+ ></uui-toggle>
626
+ </uui-form-layout-item>
627
+ ${text('nextLabel', 'Next button')}
628
+ ${text('previousLabel', 'Back button')}`
629
+ : nothing
630
+ }
225
631
 
226
- <h4>Fields</h4>
227
- <uui-table>
228
- <uui-table-head>
229
- <uui-table-head-cell>Caption</uui-table-head-cell>
230
- <uui-table-head-cell>Alias</uui-table-head-cell>
231
- <uui-table-head-cell>Type</uui-table-head-cell>
232
- <uui-table-head-cell>Choices</uui-table-head-cell>
233
- <uui-table-head-cell>Required</uui-table-head-cell>
234
- <uui-table-head-cell>Sensitive</uui-table-head-cell>
235
- <uui-table-head-cell></uui-table-head-cell>
236
- </uui-table-head>
237
- ${repeat(
238
- this.#fields,
239
- (_field, index) => index,
240
- (field, index) => html`
241
- <uui-table-row>
242
- <uui-table-cell>
243
- <uui-input
244
- .value=${field.caption ?? ''}
245
- @change=${(event) => {
246
- field.caption = event.target.value
247
- this.#touch()
248
- }}
249
- ></uui-input>
250
- </uui-table-cell>
251
- <uui-table-cell>
252
- <uui-input
253
- .value=${field.alias ?? ''}
254
- @change=${(event) => {
255
- field.alias = event.target.value
256
- this.#touch()
257
- }}
258
- ></uui-input>
259
- </uui-table-cell>
260
- <uui-table-cell>
261
- <select
262
- @change=${(event) => {
263
- field.type = event.target.value
264
- if (!CHOICE_TYPES.has(field.type)) field.values = []
265
- this.#touch()
266
- }}
267
- >
268
- ${FIELD_TYPES.map(
269
- (type) =>
270
- html`<option value=${type} ?selected=${type === field.type}>${type}</option>`,
271
- )}
272
- </select>
273
- </uui-table-cell>
274
- <uui-table-cell>
275
- ${
276
- CHOICE_TYPES.has(field.type)
277
- ? html`<uui-input
278
- placeholder="One, Two, Three"
279
- .value=${(field.values ?? []).join(', ')}
280
- @change=${(event) => {
281
- field.values = event.target.value
282
- .split(',')
283
- .map((value) => value.trim())
284
- .filter(Boolean)
285
- this.#touch()
286
- }}
287
- ></uui-input>`
288
- : nothing
289
- }
290
- </uui-table-cell>
291
- <uui-table-cell>
292
- <uui-toggle
293
- .checked=${field.mandatory === true}
294
- @change=${(event) => {
295
- field.mandatory = event.target.checked
296
- this.#touch()
297
- }}
298
- ></uui-toggle>
299
- </uui-table-cell>
300
- <uui-table-cell>
301
- <uui-toggle
302
- .checked=${field.sensitive === true}
303
- @change=${(event) => {
304
- field.sensitive = event.target.checked
305
- this.#touch()
306
- }}
307
- ></uui-toggle>
308
- </uui-table-cell>
309
- <uui-table-cell>
310
- <uui-button
311
- look="secondary"
312
- color="danger"
313
- label="Remove"
314
- @click=${() => {
315
- this.#fields.splice(index, 1)
316
- this.#touch()
317
- }}
318
- ></uui-button>
319
- </uui-table-cell>
320
- </uui-table-row>
321
- `,
322
- )}
323
- </uui-table>
632
+ <h4>Layout</h4>
633
+ ${this._editing.pages.map((page, p) => this.#page(page, p))}
324
634
  <div class="actions">
325
- <uui-button
326
- look="secondary"
327
- label="Add a field"
328
- @click=${() => {
329
- this.#fields.push({
330
- alias: '',
331
- type: 'shortAnswer',
332
- caption: '',
333
- mandatory: false,
334
- sensitive: false,
335
- values: [],
336
- accept: [],
337
- })
338
- this.#touch()
339
- }}
340
- ></uui-button>
635
+ <uui-button look="secondary" label="Add a page" @click=${() => this.#apply(addPage)}></uui-button>
341
636
  <uui-button look="primary" label="Save" ?disabled=${this._busy} @click=${() => this.#save()}></uui-button>
342
637
  <uui-button look="secondary" label="Cancel" @click=${() => {
343
638
  this._editing = undefined
@@ -349,6 +644,61 @@ export default class CromboloFormsOverviewElement extends UmbLitElement {
349
644
  }
350
645
 
351
646
  static styles = css`
647
+ .page {
648
+ border: 1px solid var(--uui-color-border, #ddd);
649
+ border-radius: var(--uui-border-radius, 3px);
650
+ padding: var(--uui-size-space-4, 12px);
651
+ margin-bottom: var(--uui-size-space-4, 12px);
652
+ }
653
+ .group {
654
+ border-left: 3px solid var(--uui-color-divider-emphasis, #ccc);
655
+ padding: var(--uui-size-space-3, 9px) 0 var(--uui-size-space-3, 9px)
656
+ var(--uui-size-space-4, 12px);
657
+ margin: var(--uui-size-space-3, 9px) 0;
658
+ }
659
+ .bar {
660
+ display: flex;
661
+ flex-wrap: wrap;
662
+ align-items: center;
663
+ gap: var(--uui-size-space-2, 6px);
664
+ margin-bottom: var(--uui-size-space-3, 9px);
665
+ }
666
+ .columns {
667
+ display: inline-flex;
668
+ align-items: center;
669
+ gap: var(--uui-size-space-2, 6px);
670
+ }
671
+ .preview {
672
+ display: grid;
673
+ gap: var(--uui-size-space-2, 6px);
674
+ margin-bottom: var(--uui-size-space-3, 9px);
675
+ }
676
+ .preview span {
677
+ padding: var(--uui-size-space-2, 6px);
678
+ background: var(--uui-color-surface-alt, #f3f3f5);
679
+ border-radius: var(--uui-border-radius, 3px);
680
+ font-size: 0.9em;
681
+ overflow: hidden;
682
+ text-overflow: ellipsis;
683
+ white-space: nowrap;
684
+ }
685
+ uui-table-row[draggable='true'] {
686
+ cursor: grab;
687
+ }
688
+ .settings {
689
+ display: grid;
690
+ gap: var(--uui-size-space-2, 6px);
691
+ }
692
+ .settings label {
693
+ display: grid;
694
+ gap: 2px;
695
+ font-size: 0.9em;
696
+ }
697
+ .offered {
698
+ margin: var(--uui-size-space-2, 6px) 0 0;
699
+ color: var(--uui-color-text-alt, #555);
700
+ font-size: 0.9em;
701
+ }
352
702
  :host {
353
703
  display: block;
354
704
  padding: var(--uui-size-layout-1, 24px);
@@ -2,7 +2,7 @@
2
2
  "$schema": "../../../backoffice-dist/dist/umbraco-package-schema.json",
3
3
  "name": "Crombolo Localizations",
4
4
  "id": "Crombolo.Localizations",
5
- "version": "0.10.0",
5
+ "version": "0.12.1",
6
6
  "allowPublicAccess": false,
7
7
  "extensions": [
8
8
  {