From 87c7e2dddfdd449eb3df923b8879ec9c426a04e7 Mon Sep 17 00:00:00 2001 From: Stuart Romanek Date: Wed, 31 Jul 2024 09:22:27 -0400 Subject: [PATCH 01/15] wip --- .../@apostrophecms/asset/lib/globalIcons.js | 2 + .../ui/apos/components/AposInputArray.vue | 237 ++++++++++++++---- .../ui/apos/components/AposInputWrapper.vue | 77 +++--- .../schema/ui/apos/logic/AposInputArray.js | 109 +++++++- .../schema/ui/apos/scss/AposInputArray.scss | 144 ++++++++--- .../apos/components/AposContextMenuItem.vue | 126 +++++----- 6 files changed, 508 insertions(+), 187 deletions(-) diff --git a/modules/@apostrophecms/asset/lib/globalIcons.js b/modules/@apostrophecms/asset/lib/globalIcons.js index 3e4b7f7cfd..7bc1b2455c 100644 --- a/modules/@apostrophecms/asset/lib/globalIcons.js +++ b/modules/@apostrophecms/asset/lib/globalIcons.js @@ -11,7 +11,9 @@ module.exports = { 'apple-keyboard-shift': 'AppleKeyboardShift', 'archive-arrow-down-icon': 'ArchiveArrowDown', 'archive-arrow-up-icon': 'ArchiveArrowUp', + 'arrow-collapse-vertical-icon': 'ArrowCollapseVertical', 'arrow-down-icon': 'ArrowDown', + 'arrow-expand-vertical-icon': 'ArrowExpandVertical', 'arrow-left-icon': 'ArrowLeft', 'arrow-right-icon': 'ArrowRight', 'arrow-up-icon': 'ArrowUp', diff --git a/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue b/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue index da69bd2edc..5683a60513 100644 --- a/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +++ b/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue @@ -5,19 +5,20 @@ :uid="uid" :items="next" :display-options="displayOptions" - :modifiers="[ - ...field.style === 'table' ? ['full-width'] : [] - ]" + :modifiers="[...(field.style === 'table' ? ['full-width'] : [])]" :meta="arrayMeta" > + + diff --git a/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js b/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js index 12d30bdc66..b968375546 100644 --- a/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +++ b/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js @@ -16,7 +16,7 @@ export default { AposInputFollowingMixin, AposInputConditionalFieldsMixin ], - emits: ['validate'], + emits: [ 'validate' ], props: { generation: { type: Number, @@ -33,7 +33,28 @@ export default { items, isDragging: false, itemsConditionalFields: Object - .fromEntries(items.map(({ _id }) => [_id, getConditionTypesObject()])) + .fromEntries(items.map(({ _id }) => [ _id, getConditionTypesObject() ])), + inlineContextMenu: [ + { + label: this.$t('apostrophe:moveUp'), + action: 'move-up' + }, + { + label: this.$t('apostrophe:moveDown'), + action: 'move-down' + }, + { + label: this.$t('apostrophe:duplicate'), + action: 'duplicate' + }, + { + label: this.$t('apostrophe:remove'), + action: 'remove', + modifiers: [ 'danger' ] + } + ], + emptyWhenIcon: this.field?.whenEmpty?.icon || 'text-box-multiple-icon', + emptyWhenLabel: this.field?.whenEmpty?.label || 'apostrophe:noItemsAdded' }; }, computed: { @@ -43,25 +64,24 @@ export default { isInlineStandard() { return this.field.style !== 'table' && this.field.inline; }, + isDisabled() { + return this.field.draggable === false || this.field.readOnly || this.next.length <= 1 || false; + }, // required by the conditional fields mixin schema() { return this.field.schema; }, - alwaysExpand() { - return alwaysExpand(this.field, this.schema); - }, listId() { return `sortableList-${createId()}`; }, dragOptions() { return { - // disabled: !this.field.draggable || this.field.readOnly || this.next.length <= 1, - disabled: this.field.readOnly || this.next.length <= 1 || false, + disabled: this.isDisabled, ghostClass: 'apos-is-ghost', handle: this.isInlineTable ? '.apos-drag-handle' : '.apos-input-array-inline-header', dragClass: 'apos-is-dragging', forceFallback: true, - dragoverBubble: true + fallbackTolerance: 5 }; }, itemLabel() { @@ -132,13 +152,6 @@ export default { await this.evaluateExternalConditions(); this.setItemsConditionalFields(); } - // if (this.field.inline && this.field.style !== 'table') { - // console.log(this.$refs); - // console.log(this.schemaRefs); - // this.schemaRefs.forEach(c => { - // console.log(c.$el.getBoundingClientRect()); - // }); - // } }, methods: { toggleAll(open) { @@ -147,34 +160,19 @@ export default { open })); }, - startDragging() { + startDragging(event) { + const dragId = event.item.getAttribute('data-id'); this.isDragging = true; - // this.toggleAll(false); + this.disengageAll(); + this.toggleEngage(null, dragId); }, - stopDragging(hello) { + stopDragging(event) { this.isDragging = false; document.getSelection().removeAllRanges(); + this.focusElement(event.item.getAttribute('data-id')); }, getInlineMenuItems(index) { - const menu = [ - { - label: 'Duplicate', - action: 'duplicate' - }, - { - label: 'Move Up', - action: 'move-up' - }, - { - label: 'Move Down', - action: 'move-down' - }, - { - label: 'Delete', - action: 'delete', - modifiers: [ 'danger' ] - } - ]; + const menu = klona(this.inlineContextMenu); if (index === 0) { menu.find(i => i.action === 'move-up').modifiers = [ 'disabled' ]; } @@ -183,12 +181,39 @@ export default { } return menu; }, + toggleEngage(event, id) { + const elId = + event.target.getAttribute('data-id') + ? event.target.getAttribute('data-id') + : event.target.closest('[data-id]').getAttribute('data-id'); + if (elId) { + if (this.items.find(i => i._id === elId)) { + event.preventDefault(); + const was = this.items.find(i => i._id === elId).engaged; + this.disengageAll(); + this.items.find(i => i._id === elId).engaged = !was; + } else { + this.disengageAll(); + } + } + }, + disengageAll() { + this.items = this.items.map(i => { + return { + ...i, + engaged: false + }; + }); + }, + disengage(id) { + const wasEngaged = this.items.find(i => i._id === id).engaged; + if (wasEngaged) { + this.items.find(i => i._id === id).engaged = false; + } + }, moveUpdate({ oldIndex, newIndex }) { - console.log('hi move update'); - console.log(oldIndex); - console.log(newIndex); if (oldIndex !== newIndex) { this.items = this.items.map((elem, index) => { return index === oldIndex @@ -227,7 +252,7 @@ export default { return 'max'; } if (value.length && this.field.fields && this.field.fields.add) { - const [uniqueFieldName, uniqueFieldSchema] = Object.entries(this.field.fields.add).find(([, subfield]) => subfield.unique) || []; + const [ uniqueFieldName, uniqueFieldSchema ] = Object.entries(this.field.fields.add).find(([ , subfield ]) => subfield.unique) || []; if (uniqueFieldName) { const duplicates = this.next .map(item => @@ -290,22 +315,25 @@ export default { schemaInput: { data: this.newInstance() }, - open: true + open: true, + engaged: false }); this.setItemsConditionalFields(_id); - // this.openInlineItem(_id); + this.focusElement(_id); }, duplicate(originalId, originalIndex) { const original = this.items.find(i => i._id === originalId); const titleField = this.field.titleField || null; + const id = createId(); const dup = { - _id: createId(), + _id: id, schemaInput: klona(original.schemaInput), - open: false + open: false, + engaged: false }; const titleFieldVal = get(dup.schemaInput.data, titleField); if (titleField) { - dup.schemaInput.data[titleField] = `Duplicate of ${titleFieldVal}`; + dup.schemaInput.data[titleField] = `${this.$t('apostrophe:duplicateOf')} ${titleFieldVal}`; } if (originalIndex + 1 === this.items.length) { @@ -313,6 +341,7 @@ export default { } else { this.items.splice(originalIndex + 1, 0, dup); } + this.focusElement(id); }, newInstance() { const instance = {}; @@ -328,18 +357,58 @@ export default { const item = this.items.find(item => item._id === id); return get(item.schemaInput.data, titleField) || `Item ${index + 1}`; }, - openInlineItem(id) { - this.items.forEach(item => { - item.open = (item._id === id) || this.alwaysExpand; - }); + toggleOpenInlineItem(event, item) { + if (event) { + const elId = + event.target.getAttribute('data-id') || + event.target.closest('[data-id]').getAttribute('data-id'); + if (this.items.find(i => i._id === elId)) { + event.preventDefault(); + const was = this.items.find(i => i._id === elId).open; + this.items.find(i => i._id === elId).open = !was; + } + } + }, + moveEngaged($e, id, direction) { + const item = this.items.find(i => i._id === id); + const index = this.items.indexOf(item); + + if ( + ((index + direction) === this.items.length) || + ((index + direction < 0)) + ) { + // already first or last, don't move + return; + } + + if (this.items.find(i => i._id === id).engaged) { + $e.stopImmediatePropagation(); + $e.preventDefault(); + this.moveUpdate({ + oldIndex: index, + newIndex: index + direction + }); + this.focusElement(id); + } }, - closeInlineItem(id) { - this.items.forEach(item => { - item.open = this.alwaysExpand; + focusElement(id) { + this.$nextTick(() => { + if (this.$refs.root.$el.querySelector(`[data-id="${id}"]`)) { + this.$refs.root.$el.querySelector(`[data-id="${id}"]`).focus(); + } }); }, - toggleOpenInlineItem(item) { - item.open = !item.open; + scrollToElement(id) { + this.$nextTick(() => { + if (this.$refs.root.$el.querySelector(`[data-id="${id}"]`)) { + const el = this.$refs.root.$el.querySelector(`[data-id="${id}"]`); + el.setAttribute('tabindex', '-1'); + this.$refs.root.$el.querySelector(`[data-id="${id}"] .apos-input-array-inline-header`).scrollIntoView({ + behavior: 'smooth' + }); + el.setAttribute('tabindex', '0'); + } + }); }, getFollowingValues(item) { return this.computeFollowingValues(item.schemaInput.data); @@ -358,7 +427,6 @@ export default { ); }, inlineMenuHandler(event, { index, id }) { - console.log(event); switch (event) { case 'move-up': this.moveUpdate({ @@ -372,39 +440,26 @@ export default { newIndex: index + 1 }); break; - case 'delete': + case 'remove': this.remove(id); break; case 'duplicate': this.duplicate(id, index); break; - default: - console.log(`Sorry, we are out of ${expr}.`); } - } } }; function modelItems(items, field, schema) { return items.map(item => { - // const open = alwaysExpand(field, schema); return { _id: item._id || createId(), schemaInput: { data: item || {} }, - open: false + open: false, + engaged: false }; }); } - -function alwaysExpand(field, schema) { - if (!field.inline) { - return false; - } - if (field.inline.alwaysExpand === undefined) { - return schema.length < 3; - } - return field.inline.alwaysExpand; -} diff --git a/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss b/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss index ee63846cbc..8bf2bac120 100644 --- a/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +++ b/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss @@ -27,20 +27,15 @@ } } -.apos-is-ghost { - opacity: 0.5; - background: var(--a-base-4); -} - .apos-input-array-inline-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; - margin-bottom: $spacing-base; padding: $spacing-triple 0; border: 1px solid var(--a-base-9); color: var(--a-base-8); + border-radius: var(--a-border-radius); } .apos-input-array-inline-empty-label { @@ -51,69 +46,11 @@ .apos-input-array-inline-table { @include type-label; - position: relative; - left: -35px; - min-width: calc(100% + 35px); - width: max-content; + width: 100%; margin: 0 0 $spacing-base; border-collapse: collapse; - th { - height: 40px; - padding-right: $spacing-base; - padding-left: $spacing-base; - border: 1px solid var(--a-base-9); - text-align: left; - background-color: var(--a-base-10); - } - - .apos-table-cell--hidden { - padding-left: 5px; - border: none; - cursor: pointer; - background-color: transparent; - } - - td, - :deep(td) { - padding: $spacing-base; - border: 1px solid var(--a-base-9); - vertical-align: middle; - text-align: center; - transition: background-color 300ms ease; - background-color: var(--a-background-primary); - } - - td.apos-input-array-inline-item-controls { - /* stylelint-disable-line selector-no-qualifying-type */ - width: 15px; - min-width: 15px; - border: none; - background-color: transparent; - } - - tr.apos-is-ghost, - :deep(tr.apos-is-ghost) { - /* stylelint-disable-line selector-no-qualifying-type */ - td, - &:hover td { - background: var(--a-base-4); - } - } - - tr:hover, - :deep(tr:hover) { - td { - background-color: var(--a-base-10); - } - - td.apos-input-array-inline-item-controls { - /* stylelint-disable-line selector-no-qualifying-type */ - background-color: transparent; - } - } - :deep(.apos-field__info) { padding-top: 0; } @@ -122,10 +59,6 @@ display: none; } - :deep(.apos-input-wrapper) { - padding: 0 4px; - } - :deep(.apos-input--select) { min-width: 130px; } @@ -170,10 +103,79 @@ } } +.apos-input-array-inline-table-row { + border-right: 1px solid var(--a-base-9); + border-bottom: 1px solid var(--a-base-9); + border-left: 1px solid var(--a-base-9); + + &:hover, + &:hover :deep(td) { + &, td { + background-color: var(--a-base-10); + } + } + + &.apos-is-ghost, + :deep(.apos-is-ghost) { + opacity: 0; + } + + &.apos-is-dragging, + :deep(.apos-is-dragging) { + opacity: 1 !important; + display: flex; + align-items: center; + outline: 2px solid var(--a-primary-transparent-50); + + .apos-input-array-inline-table-cell--controls, + .apos-input-array-inline-table-cell--controls--menu { + flex: 0; + width: auto; + background-color: var(--a-background-primary); + } + } +} + +.apos-input-array-inline-table-cell, +.apos-input-array-inline-table-row :deep(td), +:deep(.apos-input-array-inline-table-cell) { + padding: $spacing-base $spacing-half; + vertical-align: middle; + text-align: center; + transition: background-color 300ms ease; + background-color: var(--a-background-primary); + + &:focus { + border: 1px solid var(--a-primary-transparent-50); + } + + &.apos-input-array-inline-table-cell--controls { + width: 15px; + padding: 0 $spacing-base 0 $spacing-base + $spacing-half; + } + + &.apos-input-array-inline-table-cell--controls--menu { + padding: 0 $spacing-base 0 $spacing-half; + :deep(.apos-context-menu) { + width: 30px; + } + } +} + +.apos-input-array-inline-table-header-cell { + height: 40px; + padding-right: $spacing-base; + padding-left: $spacing-base; + text-align: left; + background-color: var(--a-base-9); +} + .apos-input-array-inline-header { display: flex; justify-content: space-between; - padding: $spacing-half; + padding: 0 $spacing-half; + transition: all 0.3s var(--a-transition-timing-bounce); + &:hover { cursor: grab; } @@ -181,6 +183,22 @@ &:hover:active { cursor: grabbing; } + + :deep(.apos-button) { + color: currentColor; + &:focus { + color: currentColor; + background-color: transparent; + } + &:hover { + outline: 1px solid var(--a-base-6); + } + } + + :deep(.apos-button--subtle.apos-is-active) { + color: currentColor; + outline: 1px solid var(--a-base-7); + } } .apos-input-array-inline-header-ui { @@ -225,9 +243,9 @@ margin: 0; padding-top: $spacing-base; padding-bottom: $spacing-base; + color: currentColor; text-align: left; transition: background-color 400ms ease; - // grid-column: 2; } .apos-input-array-inline-all-controls { @@ -248,16 +266,20 @@ gap: $spacing-base; .apos-input-array-inline-item { + @include apos-transition(); overflow: hidden; background-color: var(--a-base-10); border: 1px solid var(--a-base-9); padding: 0; border-radius: var(--a-border-radius); - transition: all 400ms; + outline: 1px solid transparent; &.apos-is-dragging { + opacity: 0 !important; + } + + &.apos-is-ghost { border-color: var(--a-primary-transparent-50); - // opacity: 1 !important; box-shadow: var(--a-box-shadow) } @@ -266,6 +288,35 @@ background: var(--a-base-8); } } + + &:focus { + outline: 1px solid var(--a-primary-transparent-50); + box-shadow: var(--a-box-shadow); + } + + &--engaged, + &--engaged:focus, + &.apos-is-ghost { + outline: 2px solid var(--a-primary-transparent-50); + & > .apos-input-array-inline-header { + border-color: var(--a-primary-transparent-50); + background: var(--a-primary-transparent-90); + color: var(--a-text-inverted) + } + } + + :deep(.apos-input-array-inline-empty) { + border-color: var(--a-base-7); + background-color: var(--a-background-primary); + + .apos-input-array-inline-empty-label { + color: var(--a-text-primary); + } + + .material-design-icon__svg { + fill: var(--a-base-7); + } + } } :deep(.apos-schema) { @@ -285,22 +336,11 @@ margin-bottom: 0; } - // & > div { - // grid-column: 2; - // } - - &.apos-input-array-inline-item--active { + &.apos-input-array-inline-table-row--active { background-color: var(--a-base-10); - border-bottom: 1px solid var(--a-base-6); } - &.apos-input-array-inline-item--active > div { - display: block; - } - - .apos-input-array-inline-label, - .apos-input-array-inline-item-controls, - .apos-input-array-inline-item-controls--remove { + &.apos-input-array-inline-table-row > div { display: block; } @@ -310,32 +350,20 @@ .apos-input-array-inline-item-controls { padding: $spacing-base; - // grid-column: 1; - // grid-row: 1; - } - - .apos-input-array-inline-item-controls--remove { - // grid-column: 3; - // grid-row: 1; } } } +.apos-input-array-inline-table-row--engaged { + outline: 2px solid var(--a-primary-transparent-50); +} + +.apos-input-array-inline-add-button { + margin-top: $spacing-base; +} + .apos-input-array-inline-standard--is-dragging { :deep(*::selection) { background-color: transparent; } } - -.apos-flip-list-leave-to { - opacity: 0; -} - -// // .collapse-enter-active, -// // .collapse-leave-active { -// // transition: height 200ms linear; -// // } -// // .collapse-enter, -// // .collapse-leave-to { -// // height: 0; -// // } diff --git a/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue b/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue index 70fecee4a1..6037e8df27 100644 --- a/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +++ b/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue @@ -247,6 +247,7 @@ async function setDropdownPosition() { function handleKeyboard(event) { if (event.key === 'Escape') { + event.stopImmediatePropagation(); hide(); } } diff --git a/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue b/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue index 921c900338..57a26670ec 100644 --- a/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +++ b/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue @@ -3,6 +3,7 @@ v-apos-tooltip="tooltip" class="apos-indicator" :aria-hidden="decorative" + :tabindex="decorative ? '-1' : '0'" > Date: Mon, 5 Aug 2024 14:22:35 -0400 Subject: [PATCH 04/15] more --- .../ui/apos/components/AposInputArray.vue | 54 +++++------ .../schema/ui/apos/logic/AposInputArray.js | 94 ++++++++++++------- .../schema/ui/apos/scss/AposInputArray.scss | 29 ++++-- 3 files changed, 110 insertions(+), 67 deletions(-) diff --git a/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue b/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue index b28d1f4636..360018b52f 100644 --- a/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +++ b/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue @@ -48,11 +48,12 @@
-
+ {{ $t(subfield.label) }} @@ -75,7 +76,7 @@ :key="item._id" v-model="item.schemaInput" :data-id="item._id" - tabindex="0" + :tabindex="isDraggable ? '0' : '-1'" class="apos-input-array-inline-table-row" :meta="arrayMeta[item._id]?.aposMeta" :class="{ @@ -92,30 +93,30 @@ field-style="table" @update:model-value="setItemsConditionalFields(item._id)" @validate="emitValidate()" - @keydown.space="toggleEngage($event, item._id)" - @keydown.enter="toggleEngage($event, item._id)" - @keydown.escape="disengage(item._id)" - @keydown.arrow-up="moveEngaged($event, item._id, -1)" - @keydown.arrow-down="moveEngaged($event, item._id, 1)" + @keydown.space="isDraggable ? toggleEngage($event, item._id) : {}" + @keydown.enter="isDraggable ? toggleEngage($event, item._id) : {}" + @keydown.arrow-up="isDraggable ? moveEngaged($event, item._id, -1) : {}" + @keydown.arrow-down="isDraggable ? moveEngaged($event, item._id, 1) : {}" > @@ -176,21 +178,21 @@ 'apos-input-array-inline-item--open': item.open, 'apos-input-array-inline-item--engaged': item.engaged }" - @keydown.exact.space="toggleOpenInlineItem($event, item)" - @keydown.shift.space="toggleEngage($event, item._id)" - @keydown.enter="toggleEngage($event, item._id)" - @keydown.escape="disengage(item._id)" - @keydown.arrow-up="moveEngaged($event, item._id, -1)" - @keydown.arrow-down="moveEngaged($event, item._id, 1)" + @keydown.exact.space="isDraggable ? toggleOpenInlineItem($event) : {}" + @keydown.shift.space="isDraggable ? toggleEngage($event, item._id) : {}" + @keydown.enter="isDraggable ? toggleEngage($event, item._id) : {}" + @keydown.arrow-up="isDraggable ? moveEngaged($event, item._id, -1) : {}" + @keydown.arrow-down="isDraggable ? moveEngaged($event, item._id, 1) : {}" >
@@ -214,7 +216,7 @@ type="subtle" :modifiers="['inline', 'no-motion']" :icon-only="true" - @click="toggleOpenInlineItem(null, item)" + @click="toggleOpenInlineItem($event)" @keydown.space="$event.stopImmediatePropagation()" @keydown.enter="$event.stopImmediatePropagation()" /> @@ -238,11 +240,7 @@ />
- +
@@ -291,7 +291,7 @@ class="apos-input-array-inline-add-button" :label="itemLabel" icon="plus-icon" - :disabled="disableAdd()" + :disabled="isAddDisabled" :modifiers="['block']" @click="add" /> diff --git a/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js b/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js index b968375546..9460654ddb 100644 --- a/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +++ b/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js @@ -34,25 +34,6 @@ export default { isDragging: false, itemsConditionalFields: Object .fromEntries(items.map(({ _id }) => [ _id, getConditionTypesObject() ])), - inlineContextMenu: [ - { - label: this.$t('apostrophe:moveUp'), - action: 'move-up' - }, - { - label: this.$t('apostrophe:moveDown'), - action: 'move-down' - }, - { - label: this.$t('apostrophe:duplicate'), - action: 'duplicate' - }, - { - label: this.$t('apostrophe:remove'), - action: 'remove', - modifiers: [ 'danger' ] - } - ], emptyWhenIcon: this.field?.whenEmpty?.icon || 'text-box-multiple-icon', emptyWhenLabel: this.field?.whenEmpty?.label || 'apostrophe:noItemsAdded' }; @@ -64,8 +45,48 @@ export default { isInlineStandard() { return this.field.style !== 'table' && this.field.inline; }, - isDisabled() { - return this.field.draggable === false || this.field.readOnly || this.next.length <= 1 || false; + isDraggable() { + if (this.field.draggable === false) { + return false; + } + if (this.field.readOnly) { + return false; + } + if (this.next.length > 1) { + return true; + } + return true; + }, + isAddDisabled() { + return this.field.readOnly || (this.field.max && (this.items.length >= this.field.max)); + }, + inlineContextMenu() { + const menu = [ + { + label: this.$t('apostrophe:remove'), + action: 'remove', + modifiers: [ 'danger' ] + } + ]; + if (this.field.duplicate !== false) { + menu.unshift({ + label: this.$t('apostrophe:duplicate'), + action: 'duplicate' + }); + } + if (this.isDraggable) { + menu.unshift( + { + label: this.$t('apostrophe:moveUp'), + action: 'move-up' + }, + { + label: this.$t('apostrophe:moveDown'), + action: 'move-down' + } + ); + } + return menu; }, // required by the conditional fields mixin schema() { @@ -76,7 +97,7 @@ export default { }, dragOptions() { return { - disabled: this.isDisabled, + disabled: !this.isDraggable, ghostClass: 'apos-is-ghost', handle: this.isInlineTable ? '.apos-drag-handle' : '.apos-input-array-inline-header', dragClass: 'apos-is-dragging', @@ -173,14 +194,21 @@ export default { }, getInlineMenuItems(index) { const menu = klona(this.inlineContextMenu); - if (index === 0) { + if (index === 0 && menu.find(i => i.action === 'move-up')) { menu.find(i => i.action === 'move-up').modifiers = [ 'disabled' ]; } - if (index + 1 === this.items.length) { + if (index + 1 === this.items.length && menu.find(i => i.action === 'move-down')) { menu.find(i => i.action === 'move-down').modifiers = [ 'disabled' ]; } return menu; }, + getTableHeaderClass(field, baseClass) { + let label = this.$t(field.label); + const validChars = /[^a-zA-Z0-9_-]/g; + label = label.replace(validChars, '-'); + label = label.toLowerCase(); + return `${baseClass}--${label}`; + }, toggleEngage(event, id) { const elId = event.target.getAttribute('data-id') @@ -357,11 +385,13 @@ export default { const item = this.items.find(item => item._id === id); return get(item.schemaInput.data, titleField) || `Item ${index + 1}`; }, - toggleOpenInlineItem(event, item) { + toggleOpenInlineItem(event) { if (event) { - const elId = - event.target.getAttribute('data-id') || - event.target.closest('[data-id]').getAttribute('data-id'); + const el = + event.target.getAttribute('data-id') + ? event.target + : event.target.closest('[data-id]'); + const elId = el.getAttribute('data-id'); if (this.items.find(i => i._id === elId)) { event.preventDefault(); const was = this.items.find(i => i._id === elId).open; @@ -398,15 +428,15 @@ export default { } }); }, - scrollToElement(id) { + scrollToElement(el, id) { + this.observe(el); this.$nextTick(() => { if (this.$refs.root.$el.querySelector(`[data-id="${id}"]`)) { - const el = this.$refs.root.$el.querySelector(`[data-id="${id}"]`); - el.setAttribute('tabindex', '-1'); + // const el = this.$refs.root.$el.querySelector(`[data-id="${id}"]`); this.$refs.root.$el.querySelector(`[data-id="${id}"] .apos-input-array-inline-header`).scrollIntoView({ behavior: 'smooth' }); - el.setAttribute('tabindex', '0'); + this.observe(el); } }); }, diff --git a/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss b/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss index 8bf2bac120..411b8fae8e 100644 --- a/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +++ b/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss @@ -115,6 +115,10 @@ } } + &:focus, &:focus-visible { + outline: 1px solid var(--a-primary-transparent-50); + } + &.apos-is-ghost, :deep(.apos-is-ghost) { opacity: 0; @@ -136,10 +140,23 @@ } } +.apos-input-array-inline-table-row--engaged { + &, &:focus, &:focus-visible { + outline: 2px solid var(--a-primary-transparent-50); + } +} + +.apos-input-array-inline-table-cell-drag-handle { + &:focus, &:focus-visible { + outline: 1px solid var(--a-primary-transparent-50); + } +} + .apos-input-array-inline-table-cell, .apos-input-array-inline-table-row :deep(td), :deep(.apos-input-array-inline-table-cell) { - padding: $spacing-base $spacing-half; + padding: $spacing-base; + border: 1px solid var(--a-base-9); vertical-align: middle; text-align: center; transition: background-color 300ms ease; @@ -150,12 +167,11 @@ } &.apos-input-array-inline-table-cell--controls { - width: 15px; - padding: 0 $spacing-base 0 $spacing-base + $spacing-half; + width: 20px; + padding: 0 $spacing-base; } &.apos-input-array-inline-table-cell--controls--menu { - padding: 0 $spacing-base 0 $spacing-half; :deep(.apos-context-menu) { width: 30px; } @@ -163,6 +179,7 @@ } .apos-input-array-inline-table-header-cell { + @include type-base; height: 40px; padding-right: $spacing-base; padding-left: $spacing-base; @@ -354,10 +371,6 @@ } } -.apos-input-array-inline-table-row--engaged { - outline: 2px solid var(--a-primary-transparent-50); -} - .apos-input-array-inline-add-button { margin-top: $spacing-base; } From cd4cc470047d4235199841847e72e740567a10c3 Mon Sep 17 00:00:00 2001 From: Stuart Romanek Date: Tue, 6 Aug 2024 11:24:54 -0400 Subject: [PATCH 05/15] done --- .../schema/ui/apos/components/AposInputArray.vue | 11 ++++++----- .../schema/ui/apos/logic/AposInputArray.js | 11 +++++------ .../schema/ui/apos/scss/AposInputArray.scss | 15 ++++++++++----- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue b/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue index 360018b52f..0b6636d04a 100644 --- a/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +++ b/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue @@ -64,6 +64,7 @@ :id="listId" item-key="_id" role="list" + :class="{ 'apos-input-array-inline-array--is-dragging': isDragging }" :options="dragOptions" tag="tbody" :list="items" @@ -93,10 +94,10 @@ field-style="table" @update:model-value="setItemsConditionalFields(item._id)" @validate="emitValidate()" - @keydown.space="isDraggable ? toggleEngage($event, item._id) : {}" - @keydown.enter="isDraggable ? toggleEngage($event, item._id) : {}" - @keydown.arrow-up="isDraggable ? moveEngaged($event, item._id, -1) : {}" - @keydown.arrow-down="isDraggable ? moveEngaged($event, item._id, 1) : {}" + @keydown.stop.space="isDraggable ? toggleEngage($event, { exact: true }) : {}" + @keydown.stop.enter="isDraggable ? toggleEngage($event, { exact: true }) : {}" + @keydown.stop.arrow-up="isDraggable ? moveEngaged($event, item._id, -1) : {}" + @keydown.stop.arrow-down="isDraggable ? moveEngaged($event, item._id, 1) : {}" >