From c97a60b2c013d4726d4269e648f7a95afee4aeb8 Mon Sep 17 00:00:00 2001
From: Terry Truong <terry06890@gmail.com>
Date: Thu, 24 Mar 2022 18:12:01 +1100
Subject: Recognise click-and-hold instead of double-click

---
 src/components/Tile.vue | 121 +++++++++++++++++++++++++++++-------------------
 1 file changed, 73 insertions(+), 48 deletions(-)

(limited to 'src/components/Tile.vue')

diff --git a/src/components/Tile.vue b/src/components/Tile.vue
index 7722101..f0b506e 100644
--- a/src/components/Tile.vue
+++ b/src/components/Tile.vue
@@ -15,7 +15,7 @@ export default defineComponent({
 	data(){
 		return {
 			nonLeafHighlight: false,
-			dblClickTimer: 0, // Used to delay a click action until a double-click timeout ends
+			clickHoldTimer: 0, // Used to recognise a click-and-hold event
 			zIdx: 0, // Used during transitions
 			overflow: 'visible', // Used during transitions
 		}
@@ -121,58 +121,69 @@ export default defineComponent({
 		},
 	},
 	methods: {
-		// For click handling
-		onLeafClick(evt: UIEvent){
+		// Leaf click handling
+		onLeafMouseDown(){
+			clearTimeout(this.clickHoldTimer);
+			this.clickHoldTimer = setTimeout(() => {
+				this.clickHoldTimer = 0;
+				this.onLeafClickHold();
+			}, this.options.clickHoldDuration);
+		},
+		onLeafMouseUp(){
+			if (this.clickHoldTimer > 0){
+				clearTimeout(this.clickHoldTimer);
+				this.clickHoldTimer = 0;
+				this.onLeafClick();
+			}
+		},
+		onLeafClick(){
 			if (!this.isExpandable){
-				console.log('Ignored click on non-expandable leaf node');
+				console.log('Ignored click on non-expandable node');
 				return;
 			}
-			let prepForTransition = () => {
-				// Temporary changes to prevent content overlap and overflow
-				this.zIdx = 1;
-				this.overflow = 'hidden';
-				setTimeout(() => {this.zIdx = 0; this.overflow = 'visible';}, this.options.transitionDuration);
-			};
-			if (evt.detail == 1){
-				this.dblClickTimer = setTimeout(() => {
-					this.$emit('leaf-clicked', {layoutNode: this.layoutNode, domNode: this.$el});
-					prepForTransition();
-				}, this.options.dblClickWait);
-			} else if (evt.detail == 2){
-				clearTimeout(this.dblClickTimer);
-				this.$emit('leaf-dbl-clicked', this.layoutNode);
-				prepForTransition();
+			this.$emit('leaf-clicked', {layoutNode: this.layoutNode, domNode: this.$el});
+			this.leafPrepTransition();
+		},
+		onLeafClickHold(){
+			if (!this.isExpandable){
+				console.log('Ignored click-hold on non-expandable node');
+				return;
 			}
+			this.$emit('leaf-click-held', this.layoutNode);
+			this.leafPrepTransition();
 		},
-		onInnerLeafClicked(data: {layoutNode: LayoutNode, domNode: HTMLElement}){
-			this.$emit('leaf-clicked', data);
+		leafPrepTransition(){ // Temporary style changes to prevent content overlap and overflow
+			this.zIdx = 1;
+			this.overflow = 'hidden';
+			setTimeout(() => {this.zIdx = 0; this.overflow = 'visible';}, this.options.transitionDuration);
 		},
-		onHeaderClick(evt: UIEvent){
+		// Non-leaf click handling
+		onHeaderMouseDown(){
 			this.nonLeafHighlight = false;
-			let prepForTransition = () => {
-				// Temporary changes to prevent content overlap and overflow
-				this.zIdx = 1;
-				setTimeout(() => {this.zIdx = 0}, this.options.transitionDuration);
-			};
-			if (evt.detail == 1){
-				this.dblClickTimer = setTimeout(() => {
-					this.$emit('header-clicked', {layoutNode: this.layoutNode, domNode: this.$el});
-					prepForTransition();
-				}, this.options.dblClickWait);
-			} else if (evt.detail == 2){
-				clearTimeout(this.dblClickTimer);
-				this.$emit('header-dbl-clicked', this.layoutNode);
-				prepForTransition();
+			clearTimeout(this.clickHoldTimer);
+			this.clickHoldTimer = setTimeout(() => {
+				this.clickHoldTimer = 0;
+				this.onHeaderClickHold();
+			}, this.options.clickHoldDuration);
+		},
+		onHeaderMouseUp(){
+			if (this.clickHoldTimer > 0){
+				clearTimeout(this.clickHoldTimer);
+				this.clickHoldTimer = 0;
+				this.onHeaderClick();
 			}
 		},
-		onInnerHeaderClicked(data: {layoutNode: LayoutNode, domNode: HTMLElement}){
-			this.$emit('header-clicked', data);
+		onHeaderClick(){
+			this.$emit('header-clicked', {layoutNode: this.layoutNode, domNode: this.$el});
+			this.nonLeafPrepForTransition();
 		},
-		onInnerLeafDblClicked(data: LayoutNode){
-			this.$emit('leaf-dbl-clicked', data);
+		onHeaderClickHold(){
+			this.$emit('header-click-held', this.layoutNode);
+			this.nonLeafPrepForTransition();
 		},
-		onInnerHeaderDblClicked(data: LayoutNode){
-			this.$emit('header-dbl-clicked', data);
+		nonLeafPrepForTransition(){ // Temporary style changes to prevent content overlap and overflow
+			this.zIdx = 1;
+			setTimeout(() => {this.zIdx = 0}, this.options.transitionDuration);
 		},
 		// For coloured-outlines on hovered-over leaf-tiles or non-leaf-headers
 		onNonLeafMouseEnter(evt: Event){
@@ -181,7 +192,19 @@ export default defineComponent({
 		onNonLeafMouseLeave(evt: Event){
 			this.nonLeafHighlight = false;
 		},
-		// For child event propagation
+		// Child event propagation
+		onInnerLeafClicked(data: {layoutNode: LayoutNode, domNode: HTMLElement}){
+			this.$emit('leaf-clicked', data);
+		},
+		onInnerHeaderClicked(data: {layoutNode: LayoutNode, domNode: HTMLElement}){
+			this.$emit('header-clicked', data);
+		},
+		onInnerLeafClickHeld(data: LayoutNode){
+			this.$emit('leaf-click-held', data);
+		},
+		onInnerHeaderClickHeld(data: LayoutNode){
+			this.$emit('header-click-held', data);
+		},
 		onInnerInfoIconClicked(data: LayoutNode){
 			this.$emit('info-icon-clicked', data);
 		}
@@ -190,31 +213,33 @@ export default defineComponent({
 	components: {
 		TileImg,
 	},
-	emits: ['leaf-clicked', 'header-clicked', 'leaf-dbl-clicked', 'header-dbl-clicked', 'info-icon-clicked'],
+	emits: ['leaf-clicked', 'header-clicked', 'leaf-click-held', 'header-click-held', 'info-icon-clicked'],
 });
 </script>
 
 <template>
 <div :style="tileStyles">
 	<tile-img v-if="isLeaf" :layoutNode="layoutNode" :tileSz="layoutNode.dims[0]" :options="options"
-		@click="onLeafClick" @info-icon-clicked="onInnerInfoIconClicked"/>
+		@mousedown="onLeafMouseDown" @mouseup="onLeafMouseUp" @info-icon-clicked="onInnerInfoIconClicked"/>
 	<div v-else :style="nonLeafStyles" ref="nonLeaf">
 		<h1 v-if="showHeader" :style="nonLeafHeaderStyles" class="hover:cursor-pointer"
-			@click="onHeaderClick($event)" @mouseenter="onNonLeafMouseEnter" @mouseleave="onNonLeafMouseLeave">
+			@mouseenter="onNonLeafMouseEnter" @mouseleave="onNonLeafMouseLeave"
+			@mousedown="onHeaderMouseDown" @mouseup="onHeaderMouseUp">
 			{{layoutNode.tolNode.name}}
 		</h1>
 		<div :style="sepSweptAreaStyles" ref="sepSweptArea"
 			:class="layoutNode?.sepSweptArea?.sweptLeft ? 'hide-right-edge' : 'hide-top-edge'">
 			<h1 v-if="layoutNode?.sepSweptArea?.sweptLeft === false"
 				:style="nonLeafHeaderStyles" class="hover:cursor-pointer"
-				@click="onHeaderClick($event)" @mouseenter="onNonLeafMouseEnter" @mouseleave="onNonLeafMouseLeave">
+				@mouseenter="onNonLeafMouseEnter" @mouseleave="onNonLeafMouseLeave"
+				@mousedown="onHeaderMouseDown" @mouseup="onHeaderMouseUp">
 				{{layoutNode.tolNode.name}}
 			</h1>
 		</div>
 		<tile v-for="child in layoutNode.children" :key="child.tolNode.name" :layoutNode="child"
 			:headerSz="headerSz" :tileSpacing="tileSpacing" :options="options"
 			@leaf-clicked="onInnerLeafClicked" @header-clicked="onInnerHeaderClicked"
-			@leaf-dbl-clicked="onInnerLeafDblClicked" @header-dbl-clicked="onInnerHeaderDblClicked"
+			@leaf-click-held="onInnerLeafClickHeld" @header-click-held="onInnerHeaderClickHeld"
 			@info-icon-clicked="onInnerInfoIconClicked"/>
 	</div>
 </div>
-- 
cgit v1.2.3

