Skip to content

Commit

Permalink
feat: allow to update numeric style value by pressing up / down key (#70
Browse files Browse the repository at this point in the history
)

* refactor: add ui-logic directory

* feat: add textual and numeric style value parser

* feat: parse more complex css value

* feat: allow to update numeric style value by pressing up / down key

* test: fix failing test case

* test: add test cases for getTextOffset
  • Loading branch information
ktsn committed Nov 23, 2018
1 parent 487da34 commit 2f33c62
Show file tree
Hide file tree
Showing 12 changed files with 875 additions and 32 deletions.
33 changes: 19 additions & 14 deletions src/view/components/StyleDeclaration.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
<template>
<div class="style-declaration">
<span class="style-declaration-prop"><StyleValue
:auto-focus="autoFocusProp"
:value="prop"
class="style-declaration-prop-text"
@input-start="$emit('input-start')"
@input="inputProp"
@input-end="finishInputProp"
/></span>
<span class="style-declaration-value"><StyleValue
:value="value"
@input-start="$emit('input-start')"
@input="inputValue"
@input-end="finishInputValue"
/></span>
<span class="style-declaration-prop">
<StyleValue
:auto-focus="autoFocusProp"
:value="prop"
class="style-declaration-prop-text"
@input-start="$emit('input-start')"
@input="inputProp"
@input-end="finishInputProp"
/>
</span>
<span class="style-declaration-value">
<StyleValue
:value="value"
@input-start="$emit('input-start')"
@input="inputValue"
@input-end="finishInputValue"
/>
</span>
</div>
</template>

Expand Down Expand Up @@ -81,6 +85,7 @@ export default Vue.extend({
<style scoped>
.style-declaration-prop::after {
content: ':';
margin-right: 0.3em;
}
.style-declaration-value::after {
Expand Down
77 changes: 75 additions & 2 deletions src/view/components/StyleValue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@
class="style-value editing"
contenteditable="true"
@input="input"
@keypress.prevent.enter="endEdit"
@keydown="onKeyDown"
@blur="endEdit"
/>
</template>

<script lang="ts">
import Vue from 'vue'
import { selectNodeContents } from '@/view/editing'
import {
selectNodeContents,
updateStyleValue,
getTextOffset
} from '@/view/ui-logic/editing'
export default Vue.extend({
name: 'StyleValue',
Expand Down Expand Up @@ -81,6 +85,75 @@ export default Vue.extend({
input(event: Event): void {
const el = event.currentTarget as HTMLDivElement
this.$emit('input', el.textContent)
},
update(offset: number): void {
const start = this.getSelectionStartOffset()
if (start === undefined) {
return
}
const { value, range } = updateStyleValue(this.value, start, offset)
if (!range) {
return
}
this.$emit('input', value)
this.$el.textContent = value
this.selectTextRange(range[0], range[1])
},
getSelectionStartOffset(): number | undefined {
const selection = window.getSelection()
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i)
if (this.$el.contains(range.startContainer)) {
// Ensure the $el only one text node
return getTextOffset(range.startContainer, range.startOffset)
}
}
return undefined
},
selectTextRange(start: number, end: number): void {
const selection = window.getSelection()
selection.removeAllRanges()
// Ensure the $el can be only one text node
const text = this.$el.childNodes[0]
if (!text) {
return
}
const range = new Range()
range.setStart(text, start)
range.setEnd(text, end)
selection.removeAllRanges()
selection.addRange(range)
},
onKeyDown(event: KeyboardEvent): void {
switch (event.key) {
case 'Enter':
event.preventDefault()
this.endEdit(event)
break
case 'Up':
case 'ArrowUp':
event.preventDefault()
this.update(1)
break
case 'Down':
case 'ArrowDown':
event.preventDefault()
this.update(-1)
break
default:
// Nothing
}
}
}
})
Expand Down
2 changes: 1 addition & 1 deletion src/view/components/VueComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import VueChild from './VueChild.vue'
import { TETemplate } from '@/parser/template/types'
import { ChildComponent } from '@/parser/script/types'
import { DocumentScope } from '@/view/store/modules/project/types'
import { resolveControlDirectives, ResolvedChild } from '../rendering'
import { resolveControlDirectives, ResolvedChild } from '../ui-logic/rendering'
export default Vue.extend({
name: 'VueComponent',
Expand Down
2 changes: 1 addition & 1 deletion src/view/components/VueNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
resolveControlDirectives,
ResolvedChild,
resolveScopedSlots
} from '../rendering'
} from '../ui-logic/rendering'
import { DraggingPlace } from '../store/modules/project/types'
import { mapValues } from '@/utils'
Expand Down
2 changes: 1 addition & 1 deletion src/view/components/VueSlot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Vue, { VNode, VNodeChildrenArrayContents } from 'vue'
import VueChild from './VueChild.vue'
import { TEElement } from '@/parser/template/types'
import { DefaultValue, ChildComponent } from '@/parser/script/types'
import { convertToSlotScope } from '@/view/rendering'
import { convertToSlotScope } from '@/view/ui-logic/rendering'
export default Vue.extend({
name: 'VueSlot',
Expand Down
10 changes: 0 additions & 10 deletions src/view/editing.ts

This file was deleted.

71 changes: 71 additions & 0 deletions src/view/ui-logic/editing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { lexStyleValue, lexToString } from './style-value-lexer'

export function selectNodeContents(el: Node): void {
// Skip if selection does not exist (in case of testing environment)
if (!('getSelection' in window)) return

const selection = window.getSelection()
const range = new Range()
range.selectNodeContents(el)
selection.removeAllRanges()
selection.addRange(range)
}

export function getTextOffset(node: Node, offset?: number): number {
if (node.nodeType === Node.TEXT_NODE) {
return offset !== undefined ? offset : (node as Text).wholeText.length
}

if (
node.nodeType === Node.COMMENT_NODE ||
node.nodeType === Node.CDATA_SECTION_NODE
) {
// Ignore comments and cdata...
return 0
}

const targetChildren =
offset !== undefined
? Array.from(node.childNodes).slice(0, offset)
: Array.from(node.childNodes)

return targetChildren.reduce((acc, c) => {
return acc + getTextOffset(c)
}, 0)
}

export function updateStyleValue(
value: string,
position: number,
offset: number
): {
value: string
range?: [number, number]
} {
return lexStyleValue(value).reduce(
(acc, n) => {
if (n.range[0] <= position && position < n.range[1]) {
if (n.type === 'numeric') {
const updated = {
...n,
value: n.value + offset
}

const str = lexToString(updated)

return {
value: acc.value + str,
range: [acc.value.length, acc.value.length + str.length]
}
}
}
return {
...acc,
value: acc.value + lexToString(n)
}
},
{
value: ''
}
)
}
2 changes: 1 addition & 1 deletion src/view/rendering.ts → src/view/ui-logic/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
TEStartTag
} from '@/parser/template/types'
import { DefaultValue } from '@/parser/script/types'
import { evalWithScope } from './eval'
import { evalWithScope } from '../eval'
import { isObject, range, mapValues } from '@/utils'

function attributeValue(
Expand Down

0 comments on commit 2f33c62

Please sign in to comment.