77 <div ref =" widgetRoot" :class =" { 'toggle-interactive': hasInteractiveView && !isInteractive }" >
88 <div
99 v-if =" reference && hasCustomWidget"
10+ :id =" widgetId"
1011 ref =" customWidget"
1112 class =" widget-custom"
12- :class =" { 'full-width': hasFullWidth }" />
13+ :class =" {
14+ 'full-width': hasFullWidth,
15+ }"
16+ :style =" customWidgetStyle" >
17+ <div ref =" customWidgetContent" class =" widget-custom__content" />
18+ <div
19+ v-if =" isResizable"
20+ class =" widget-custom__resize-handle"
21+ role =" slider"
22+ tabindex =" 0"
23+ aria-orientation =" vertical"
24+ :aria-label =" t('Widget height')"
25+ :aria-valuenow =" ariaValueNow"
26+ :aria-valuemin =" resizeMinHeight"
27+ :aria-valuemax =" resizeMaxHeight"
28+ :aria-controls =" widgetId"
29+ @pointerdown.stop.prevent =" startResize"
30+ @keydown.up.stop.prevent =" onResizeKeydown(-RESIZE_KEYBOARD_STEP)"
31+ @keydown.down.stop.prevent =" onResizeKeydown(RESIZE_KEYBOARD_STEP)" />
32+ </div >
1333
1434 <component
1535 :is =" referenceWidgetLinkComponent"
@@ -42,10 +62,12 @@ import { nextTick, ref } from 'vue'
4262import { RouterLink } from ' vue-router'
4363import NcButton from ' ../../components/NcButton/index.js'
4464import { t } from ' ../../l10n.js'
45- import { destroyWidget , hasFullWidth , hasInteractiveView , isWidgetRegistered , renderWidget } from ' ./../../functions/reference/widgets.ts'
65+ import { createElementId } from ' ../../utils/createElementId.ts'
66+ import { destroyWidget , hasFullWidth , hasInteractiveView , isResizable , isWidgetRegistered , renderWidget } from ' ./../../functions/reference/widgets.ts'
4667import { getRoute } from ' ./autolink.js'
4768
4869const IDLE_TIMEOUT = 3 * 60 * 1000 // 3 minutes outside of viewport before widget is removed from the DOM
70+ const RESIZE_KEYBOARD_STEP = 10
4971
5072export default {
5173 name: ' NcReferenceWidget' ,
@@ -73,10 +95,14 @@ export default {
7395 },
7496
7597 setup () {
98+ const widgetId = ` nc-reference-widget-${ createElementId ()} `
99+
76100 const isVisible = ref (false )
77101 // This is the widget root node
78102 const widgetRoot = ref ()
103+ const customWidget = ref ()
79104 const { width } = useElementSize (widgetRoot)
105+ const { height: customWidgetHeight } = useElementSize (customWidget)
80106
81107 useIntersectionObserver (widgetRoot, ([entry ]) => {
82108 nextTick (() => {
@@ -88,6 +114,10 @@ export default {
88114 width,
89115 isVisible,
90116 widgetRoot,
117+ customWidget,
118+ customWidgetHeight,
119+ widgetId,
120+ RESIZE_KEYBOARD_STEP ,
91121 }
92122 },
93123
@@ -96,6 +126,12 @@ export default {
96126 showInteractive: false ,
97127 rendered: false ,
98128 idleTimeout: null ,
129+ isResizing: false ,
130+ resizedHeight: null ,
131+ resizeStartY: 0 ,
132+ resizeStartHeight: 0 ,
133+ resizeMinHeight: 100 ,
134+ resizeMaxHeight: window .innerHeight - 120 ,
99135 }
100136 },
101137
@@ -108,10 +144,25 @@ export default {
108144 return hasFullWidth (this .reference .richObjectType )
109145 },
110146
147+ isResizable () {
148+ return isResizable (this .reference .richObjectType )
149+ },
150+
111151 hasCustomWidget () {
112152 return isWidgetRegistered (this .reference .richObjectType )
113153 },
114154
155+ customWidgetStyle () {
156+ if (! this .resizedHeight ) {
157+ return null
158+ }
159+ return { height: ` ${ this .resizedHeight } px !important` }
160+ },
161+
162+ ariaValueNow () {
163+ return Math .round (this .resizedHeight ?? this .customWidgetHeight )
164+ },
165+
115166 hasInteractiveView () {
116167 return isWidgetRegistered (this .reference .richObjectType ) && hasInteractiveView (this .reference .richObjectType )
117168 },
@@ -197,6 +248,11 @@ export default {
197248 },
198249
199250 beforeDestroy () {
251+ if (this .idleTimeout ) {
252+ clearTimeout (this .idleTimeout )
253+ this .idleTimout = null
254+ }
255+ this .stopResize ()
200256 this .destroyWidget ()
201257 },
202258
@@ -209,20 +265,20 @@ export default {
209265 },
210266
211267 renderWidget () {
212- if (! this .$refs .customWidget ) {
268+ if (! this .$refs .customWidgetContent ) {
213269 return
214270 }
215271
216272 if (this ? .reference ? .richObjectType === ' open-graph' ) {
217273 return
218274 }
219275
220- this .$refs .customWidget .innerHTML = ' '
276+ this .$refs .customWidgetContent .innerHTML = ' '
221277
222278 // create a separate element so we can rerender on the ref again
223279 const widget = document .createElement (' div' )
224280 widget .style = ' width: 100%;'
225- this .$refs .customWidget .appendChild (widget)
281+ this .$refs .customWidgetContent .appendChild (widget)
226282 this .$nextTick (() => {
227283 // Waiting for the ref to become available
228284 renderWidget (widget, {
@@ -239,6 +295,67 @@ export default {
239295 this .rendered = false
240296 }
241297 },
298+
299+ initResizeLimits () {
300+ // Use min/max height from rendered widget element if set
301+ const widgetEl = this .$refs .customWidgetContent ? .firstElementChild
302+ if (widgetEl) {
303+ const computedStyle = window .getComputedStyle (widgetEl)
304+ const parsedMin = parseFloat (computedStyle .minHeight )
305+ const parsedMax = parseFloat (computedStyle .maxHeight )
306+ this .resizeMinHeight = parsedMin > 0 ? parsedMin : 100
307+ this .resizeMaxHeight = Number .isFinite (parsedMax) && parsedMax > 0 ? parsedMax : (window .innerHeight - 120 )
308+ } else {
309+ this .resizeMinHeight = 100
310+ this .resizeMaxHeight = window .innerHeight - 120
311+ }
312+ },
313+
314+ startResize (event ) {
315+ if (! this .isResizable || ! this .$refs .customWidget ) {
316+ return
317+ }
318+
319+ this .initResizeLimits ()
320+ this .isResizing = true
321+ this .resizeStartY = event .clientY
322+ this .resizeStartHeight = this .$refs .customWidget .getBoundingClientRect ().height
323+
324+ window .addEventListener (' pointermove' , this .onResize )
325+ window .addEventListener (' pointerup' , this .stopResize )
326+ },
327+
328+ onResizeKeydown (delta ) {
329+ if (! this .isResizable || ! this .$refs .customWidget ) {
330+ return
331+ }
332+
333+ // Establish limits once per interaction
334+ this .initResizeLimits ()
335+
336+ const currentHeight = this .resizedHeight ?? this .resizeStartHeight
337+ const next = currentHeight + delta
338+ this .resizedHeight = Math .min (this .resizeMaxHeight , Math .max (this .resizeMinHeight , next))
339+ },
340+
341+ onResize (event ) {
342+ if (! this .isResizing || ! this .$refs .customWidget ) {
343+ return
344+ }
345+
346+ const deltaY = event .clientY - this .resizeStartY
347+ this .resizedHeight = Math .min (this .resizeMaxHeight , Math .max (this .resizeMinHeight , this .resizeStartHeight + deltaY))
348+ },
349+
350+ stopResize () {
351+ if (! this .isResizing ) {
352+ return
353+ }
354+
355+ this .isResizing = false
356+ window .removeEventListener (' pointermove' , this .onResize )
357+ window .removeEventListener (' pointerup' , this .stopResize )
358+ },
242359 },
243360}
244361< / script>
@@ -259,12 +376,52 @@ export default {
259376
260377.widget - custom {
261378 @include widget;
379+ position: relative;
262380
263381 & .full - width {
264382 width: var (-- widget- full- width, 100 % ) ! important;
265383 inset- inline- start: calc ( (var (-- widget- full- width, 100 % ) - 100 % ) / 2 * - 1 );
266384 position: relative;
267385 }
386+
387+ & __content {
388+ width: 100 % ;
389+ display: flex;
390+ flex- direction: column;
391+ }
392+
393+ & __resize- handle {
394+ position: absolute;
395+ bottom: 0 ;
396+ inset- inline- start: 0 ;
397+ width: 100 % ;
398+ height: calc (var (-- default- grid- baseline) * 2 );
399+ border- top: 1px solid var (-- color- border);
400+ margin- top: - 1px ;
401+ cursor: row- resize;
402+ touch- action: none;
403+
404+ & :: before, & :: after {
405+ content: ' ' ;
406+ position: absolute;
407+ top: 50 % ;
408+ left: 50 % ;
409+ background- color: var (-- color- border);
410+ margin- top: 1px ;
411+ transform: translateX (- 50 % );
412+ width: 30px ;
413+ height: 1px ;
414+ }
415+
416+ & :: before {
417+ margin- top: - 2px ;
418+ }
419+
420+ & : focus- visible {
421+ outline: 2px solid var (-- color- primary- element);
422+ outline- offset: - 4px ;
423+ }
424+ }
268425}
269426
270427.widget - access {
0 commit comments