Add 'scrollIntoView' to baseUIElement, autoscroll questions into view when appropriate
This commit is contained in:
parent
aec5ffba79
commit
9e97eba519
4 changed files with 78 additions and 13 deletions
|
@ -3,6 +3,8 @@
|
||||||
*
|
*
|
||||||
* Assumes a read-only configuration, so it has no 'ListenTo'
|
* Assumes a read-only configuration, so it has no 'ListenTo'
|
||||||
*/
|
*/
|
||||||
|
import {Utils} from "../Utils";
|
||||||
|
|
||||||
export default abstract class BaseUIElement {
|
export default abstract class BaseUIElement {
|
||||||
protected _constructedHtmlElement: HTMLElement
|
protected _constructedHtmlElement: HTMLElement
|
||||||
protected isDestroyed = false
|
protected isDestroyed = false
|
||||||
|
@ -41,6 +43,34 @@ export default abstract class BaseUIElement {
|
||||||
this._constructedHtmlElement?.scrollTo(0, 0)
|
this._constructedHtmlElement?.scrollTo(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ScrollIntoView(options?: {
|
||||||
|
onlyIfPartiallyHidden?: boolean
|
||||||
|
}) {
|
||||||
|
if(this._constructedHtmlElement === undefined){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let alignToTop = true;
|
||||||
|
if(options?.onlyIfPartiallyHidden){
|
||||||
|
// Is the element completely in the view?
|
||||||
|
const parentRect = Utils.findParentWithScrolling(this._constructedHtmlElement.parentElement).getBoundingClientRect();
|
||||||
|
const elementRect = this._constructedHtmlElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Check if the element is within the vertical bounds of the parent element
|
||||||
|
const topIsVisible = elementRect.top >= parentRect.top
|
||||||
|
const bottomIsVisible = elementRect.bottom <= parentRect.bottom
|
||||||
|
const inView = topIsVisible && bottomIsVisible ;
|
||||||
|
if(inView){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(topIsVisible){
|
||||||
|
alignToTop = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._constructedHtmlElement?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "start"
|
||||||
|
})
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Adds all the relevant classes, space separated
|
* Adds all the relevant classes, space separated
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import {UIEventSource} from "../../Logic/UIEventSource"
|
||||||
import TagRenderingQuestion from "./TagRenderingQuestion"
|
import TagRenderingQuestion from "./TagRenderingQuestion"
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import Combine from "../Base/Combine"
|
import Combine from "../Base/Combine"
|
||||||
import TagRenderingAnswer from "./TagRenderingAnswer"
|
import TagRenderingAnswer from "./TagRenderingAnswer"
|
||||||
import Svg from "../../Svg"
|
|
||||||
import Toggle from "../Input/Toggle"
|
import Toggle from "../Input/Toggle"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
import BaseUIElement from "../BaseUIElement"
|
||||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||||
import { Unit } from "../../Models/Unit"
|
import {Unit} from "../../Models/Unit"
|
||||||
import Lazy from "../Base/Lazy"
|
import Lazy from "../Base/Lazy"
|
||||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
import {FixedUiElement} from "../Base/FixedUiElement"
|
||||||
import { EditButton } from "./SaveButton"
|
import {EditButton} from "./SaveButton"
|
||||||
|
|
||||||
export default class EditableTagRendering extends Toggle {
|
export default class EditableTagRendering extends Toggle {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -31,10 +30,9 @@ export default class EditableTagRendering extends Toggle {
|
||||||
configuration.IsKnown(tags) &&
|
configuration.IsKnown(tags) &&
|
||||||
(configuration?.condition?.matchesProperties(tags) ?? true)
|
(configuration?.condition?.matchesProperties(tags) ?? true)
|
||||||
)
|
)
|
||||||
|
const editMode = options.editMode ?? new UIEventSource<boolean>(false)
|
||||||
super(
|
super(
|
||||||
new Lazy(() => {
|
new Lazy(() => {
|
||||||
const editMode = options.editMode ?? new UIEventSource<boolean>(false)
|
|
||||||
let rendering = EditableTagRendering.CreateRendering(
|
let rendering = EditableTagRendering.CreateRendering(
|
||||||
state,
|
state,
|
||||||
tags,
|
tags,
|
||||||
|
@ -54,6 +52,13 @@ export default class EditableTagRendering extends Toggle {
|
||||||
undefined,
|
undefined,
|
||||||
renderingIsShown
|
renderingIsShown
|
||||||
)
|
)
|
||||||
|
const self = this;
|
||||||
|
editMode.addCallback(editing => {
|
||||||
|
if(editing){
|
||||||
|
console.log("Scrolling etr into view")
|
||||||
|
self.ScrollIntoView()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CreateRendering(
|
private static CreateRendering(
|
||||||
|
@ -69,12 +74,6 @@ export default class EditableTagRendering extends Toggle {
|
||||||
|
|
||||||
if (configuration.question !== undefined && state?.featureSwitchUserbadge?.data) {
|
if (configuration.question !== undefined && state?.featureSwitchUserbadge?.data) {
|
||||||
// We have a question and editing is enabled
|
// We have a question and editing is enabled
|
||||||
const answerWithEditButton = new Combine([
|
|
||||||
answer,
|
|
||||||
new EditButton(state.osmConnection, () => {
|
|
||||||
editMode.setData(true)
|
|
||||||
}),
|
|
||||||
]).SetClass("flex justify-between w-full")
|
|
||||||
|
|
||||||
const question = new Lazy(
|
const question = new Lazy(
|
||||||
() =>
|
() =>
|
||||||
|
@ -92,7 +91,18 @@ export default class EditableTagRendering extends Toggle {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const answerWithEditButton = new Combine([
|
||||||
|
answer,
|
||||||
|
new EditButton(state.osmConnection, () => {
|
||||||
|
editMode.setData(true)
|
||||||
|
question.ScrollIntoView({
|
||||||
|
onlyIfPartiallyHidden:true
|
||||||
|
})
|
||||||
|
|
||||||
|
}),
|
||||||
|
]).SetClass("flex justify-between w-full")
|
||||||
rendering = new Toggle(question, answerWithEditButton, editMode)
|
rendering = new Toggle(question, answerWithEditButton, editMode)
|
||||||
|
|
||||||
}
|
}
|
||||||
return rendering
|
return rendering
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,8 @@ export default class QuestionBox extends VariableUiElement {
|
||||||
.filter((tr) => tr.question !== undefined)
|
.filter((tr) => tr.question !== undefined)
|
||||||
.filter((tr) => tr.question !== null)
|
.filter((tr) => tr.question !== null)
|
||||||
|
|
||||||
|
let focus: () => void = () => {};
|
||||||
|
|
||||||
const tagRenderingQuestions = tagRenderings.map(
|
const tagRenderingQuestions = tagRenderings.map(
|
||||||
(tagRendering, i) =>
|
(tagRendering, i) =>
|
||||||
new Lazy(
|
new Lazy(
|
||||||
|
@ -42,6 +44,7 @@ export default class QuestionBox extends VariableUiElement {
|
||||||
afterSave: () => {
|
afterSave: () => {
|
||||||
// We save and indicate progress by pinging and recalculating
|
// We save and indicate progress by pinging and recalculating
|
||||||
skippedQuestions.ping()
|
skippedQuestions.ping()
|
||||||
|
focus()
|
||||||
},
|
},
|
||||||
cancelButton: Translations.t.general.skip
|
cancelButton: Translations.t.general.skip
|
||||||
.Clone()
|
.Clone()
|
||||||
|
@ -49,6 +52,8 @@ export default class QuestionBox extends VariableUiElement {
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
skippedQuestions.data.push(i)
|
skippedQuestions.data.push(i)
|
||||||
skippedQuestions.ping()
|
skippedQuestions.ping()
|
||||||
|
focus()
|
||||||
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -136,5 +141,8 @@ export default class QuestionBox extends VariableUiElement {
|
||||||
|
|
||||||
this.skippedQuestions = skippedQuestions
|
this.skippedQuestions = skippedQuestions
|
||||||
this.restingQuestions = questionsToAsk
|
this.restingQuestions = questionsToAsk
|
||||||
|
focus = () => this.ScrollIntoView({
|
||||||
|
onlyIfPartiallyHidden: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
Utils.ts
17
Utils.ts
|
@ -1,4 +1,5 @@
|
||||||
import * as colors from "./assets/colors.json"
|
import * as colors from "./assets/colors.json"
|
||||||
|
import HTML = Mocha.reporters.HTML;
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
/**
|
/**
|
||||||
|
@ -1221,4 +1222,20 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
d.setUTCMilliseconds(0)
|
d.setUTCMilliseconds(0)
|
||||||
d.setUTCMinutes(0)
|
d.setUTCMinutes(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static findParentWithScrolling(element: HTMLElement): HTMLElement {
|
||||||
|
// Check if the element itself has scrolling
|
||||||
|
if (element.scrollHeight > element.clientHeight) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the element does not have scrolling, check if it has a parent element
|
||||||
|
if (!element.parentElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the element has a parent, repeat the process for the parent element
|
||||||
|
return Utils.findParentWithScrolling(element.parentElement);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue