From f20621dd07415f15edd63774326da7fcec4dc10b Mon Sep 17 00:00:00 2001 From: kim jeong yong Date: Mon, 15 Jun 2026 18:03:05 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[fix]=20Sortable=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=9C=84=EC=B9=98=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 모양/소리/장면 리스트에서 아이템 순서를 바꾸면 setData 리렌더 과정에서 내부 스크롤 컨테이너(.rcs-inner-container)의 스크롤이 맨 위로 초기화되던 문제를 수정. setData 호출 전후로 scrollTop/scrollLeft를 보존하는 Entry.Utils.runWithScrollPreserved 헬퍼를 추가하고 각 updateView에 적용. Co-Authored-By: Claude Fable 5 --- src/class/playground.js | 14 +++++++++----- src/class/scene.js | 6 +++++- src/util/utils.js | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/class/playground.js b/src/class/playground.js index 525d375a5a..ed3f147943 100644 --- a/src/class/playground.js +++ b/src/class/playground.js @@ -704,9 +704,11 @@ Entry.Playground = class Playground { updatePictureView() { if (this.pictureSortableListWidget) { - this.pictureSortableListWidget.setData({ items: [] }); - this.pictureSortableListWidget.setData({ - items: this._getSortablePictureList(), + Entry.Utils.runWithScrollPreserved(this.pictureListView_, () => { + this.pictureSortableListWidget.setData({ items: [] }); + this.pictureSortableListWidget.setData({ + items: this._getSortablePictureList(), + }); }); } this.reloadPlayground(); @@ -1102,8 +1104,10 @@ Entry.Playground = class Playground { updateSoundsView() { if (this.soundSortableListWidget) { - this.soundSortableListWidget.setData({ - items: this._getSortableSoundList(), + Entry.Utils.runWithScrollPreserved(this.soundListView_, () => { + this.soundSortableListWidget.setData({ + items: this._getSortableSoundList(), + }); }); } diff --git a/src/class/scene.js b/src/class/scene.js index e2ca9da4c9..b58c91f27a 100644 --- a/src/class/scene.js +++ b/src/class/scene.js @@ -172,7 +172,11 @@ Entry.Scene = class { updateSceneView() { const items = this._getSortableSceneList(); if (this.sceneSortableListWidget) { - setTimeout(() => this.sceneSortableListWidget.setData({ items }), 0); + setTimeout(() => { + Entry.Utils.runWithScrollPreserved(this.listView_, () => { + this.sceneSortableListWidget.setData({ items }); + }); + }, 0); } } diff --git a/src/util/utils.js b/src/util/utils.js index 4864044255..7073ebfde1 100644 --- a/src/util/utils.js +++ b/src/util/utils.js @@ -479,6 +479,28 @@ Entry.Utils.generateId = function () { return `0000${((Math.random() * Math.pow(36, 4)) << 0).toString(36)}`.substr(-4); }; +/** + * Sortable 위젯의 setData 등 리렌더 콜백 실행 중에도 + * 내부 스크롤 컨테이너(.rcs-inner-container)의 스크롤 위치를 유지한다. + * @param {HTMLElement} containerEl Sortable이 마운트된 컨테이너 + * @param {Function} callback 리스트를 갱신하는 동기 콜백 + */ +Entry.Utils.runWithScrollPreserved = function (containerEl, callback) { + const getScrollEl = () => + containerEl && containerEl.querySelector('.rcs-inner-container'); + const before = getScrollEl(); + const top = before ? before.scrollTop : 0; + const left = before ? before.scrollLeft : 0; + + callback(); + + const after = getScrollEl(); + if (after) { + after.scrollTop = top; + after.scrollLeft = left; + } +}; + Entry.Utils.randomColor = function () { const letters = '0123456789ABCDEF'; let color = '#'; From 0f30001b84a7796cc8b279a4d15c701ee9df189c Mon Sep 17 00:00:00 2001 From: kim jeong yong Date: Thu, 25 Jun 2026 10:41:58 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[fix]=20=EC=A0=84=EC=B2=B4=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EB=AC=BB=EA=B3=A0=20=EB=8B=B5?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EC=9D=B4=20=EC=95=88=20=EB=90=98=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 키 입력을 받는 숨겨진 input이 document.body에 위치해 네이티브 전체화면 진입 시 전체화면 엘리먼트의 top-layer 밖이 되어 포커스를 받지 못하던 문제. 포커스 시점과 fullscreenchange 시 input을 현재 fullscreenElement 하위로 이동시켜 해결. Co-Authored-By: Claude Opus 4.8 (1M context) --- extern/util/CanvasInput.js | 45 +++++++++++++++------------ src/class/pixi/etc/PIXICanvasInput.js | 35 ++++++++++++--------- src/class/stage.js | 28 +++++++++++++++++ 3 files changed, 73 insertions(+), 35 deletions(-) diff --git a/extern/util/CanvasInput.js b/extern/util/CanvasInput.js index 813bba72cf..8ce4ccbf85 100755 --- a/extern/util/CanvasInput.js +++ b/extern/util/CanvasInput.js @@ -9,7 +9,7 @@ * * MIT License */ -(function() { +(function () { // create a buffer that stores all inputs so that tabbing // between them is made possible. var inputs = []; // initialize the Canvas Input @@ -46,15 +46,15 @@ self._placeHolder = o.placeHolder || ''; self._value = o.value || self._placeHolder; - self._onsubmit = o.onsubmit || function() {}; + self._onsubmit = o.onsubmit || function () {}; - self._onkeydown = o.onkeydown || function() {}; + self._onkeydown = o.onkeydown || function () {}; - self._onkeyup = o.onkeyup || function() {}; + self._onkeyup = o.onkeyup || function () {}; - self._onfocus = o.onfocus || function() {}; + self._onfocus = o.onfocus || function () {}; - self._onblur = o.onblur || function() {}; + self._onblur = o.onblur || function () {}; self._cursor = false; self._cursorPos = 0; @@ -95,7 +95,7 @@ if (self._canvas) { self._canvas.addEventListener( 'mousemove', - function(e) { + function (e) { e = e || window.event; self.mousemove(e, self); }, @@ -104,7 +104,7 @@ self._canvas.addEventListener( 'mousedown', - function(e) { + function (e) { e = e || window.event; self.mousedown(e, self); }, @@ -113,7 +113,7 @@ self._canvas.addEventListener( 'touchstart', - function(e) { + function (e) { e = e || window.event; self.mousedown(e, self); }, @@ -122,7 +122,7 @@ self._canvas.addEventListener( 'mouseup', - function(e) { + function (e) { e = e || window.event; self.mouseup(e, self); }, @@ -132,7 +132,7 @@ window.addEventListener( 'mouseup', - function(e) { + function (e) { //e = e || window.event; //if (self._hasFocus && !self._mouseDown) { //self.blur(); @@ -160,7 +160,7 @@ document.body.appendChild(self._hiddenInput); self._hiddenInput.value = self._value; // setup the keydown listener - self._hiddenInput.addEventListener('keydown', function(e) { + self._hiddenInput.addEventListener('keydown', function (e) { e = e || window.event; if (self._hasFocus) { @@ -168,7 +168,7 @@ } }); // setup the keyup listener - self._hiddenInput.addEventListener('keyup', function(e) { + self._hiddenInput.addEventListener('keyup', function (e) { e = e || window.event; // update the canvas input state information from the hidden input self._value = self._hiddenInput.value; @@ -726,8 +726,8 @@ clearInterval(self._cursorInterval); } - requestAnimationFrame(function() { - self._cursorInterval = setInterval(function() { + requestAnimationFrame(function () { + self._cursorInterval = setInterval(function () { self._cursor = !self._cursor; self.render(); }, 500); // check if this is Chrome for Android (there is a bug with returning incorrect character key codes) @@ -741,6 +741,8 @@ var isMobile = typeof window.orientation !== 'undefined'; var hasHiddenFocus = false; + var inputParent = + document.fullscreenElement || document.webkitFullscreenElement || document.body; if ( isMobile && !isChromeMobile && @@ -763,11 +765,11 @@ input.style.height = 0; const form = document.createElement('form'); form.appendChild(input); - document.body.appendChild(form); + inputParent.appendChild(form); input.focus(); input.addEventListener( 'blur', - function() { + function () { if (!hasHiddenFocus) { self.blur(self); } @@ -787,6 +789,9 @@ var hasSelection = self._selection[0] > 0 || self._selection[1] > 0; hasHiddenFocus = true; + if (self._hiddenInput.parentNode !== inputParent) { + inputParent.appendChild(self._hiddenInput); + } self._hiddenInput.focus(); hasHiddenFocus = false; self._hiddenInput.selectionStart = hasSelection ? self._selection[0] : self._cursorPos; @@ -869,7 +874,7 @@ if (inputs.length > 1) { var next = inputs[self._inputsIndex + 1] ? self._inputsIndex + 1 : 0; self.blur(); - setTimeout(function() { + setTimeout(function () { inputs[next].focus(); }, 5); } @@ -1088,7 +1093,7 @@ ctx.shadowBlur = 0; } // draw the text box background - self._drawTextBox(function() { + self._drawTextBox(function () { // make sure all shadows are reset ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; @@ -1234,7 +1239,7 @@ var img = new Image(); img.src = self._backgroundImage; - img.onload = function() { + img.onload = function () { ctx.drawImage( img, 0, diff --git a/src/class/pixi/etc/PIXICanvasInput.js b/src/class/pixi/etc/PIXICanvasInput.js index 3565769eee..2f9ce66119 100644 --- a/src/class/pixi/etc/PIXICanvasInput.js +++ b/src/class/pixi/etc/PIXICanvasInput.js @@ -8,14 +8,14 @@ * MIT License */ import * as PIXI from 'pixi.js'; -(function() { +(function () { // create a buffer that stores all inputs so that tabbing // between them is made possible. const inputs = []; // initialize the Canvas Input // eslint-disable-next-line no-multi-assign - const CanvasInput = (window.PIXICanvasInput = function(o) { + const CanvasInput = (window.PIXICanvasInput = function (o) { const self = this; o = o ? o : {}; @@ -48,11 +48,11 @@ import * as PIXI from 'pixi.js'; self._selectionColor = o.selectionColor || 'rgba(179, 212, 253, 0.8)'; self._placeHolder = o.placeHolder || ''; self._value = `${o.value || self._placeHolder}`; - self._onsubmit = o.onsubmit || function() {}; - self._onkeydown = o.onkeydown || function() {}; - self._onkeyup = o.onkeyup || function() {}; - self._onfocus = o.onfocus || function() {}; - self._onblur = o.onblur || function() {}; + self._onsubmit = o.onsubmit || function () {}; + self._onkeydown = o.onkeydown || function () {}; + self._onkeyup = o.onkeyup || function () {}; + self._onfocus = o.onfocus || function () {}; + self._onblur = o.onblur || function () {}; self._cursor = false; self._cursorPos = 0; self._hasFocus = false; @@ -772,6 +772,8 @@ import * as PIXI from 'pixi.js'; // add support for mobile const isMobile = typeof window.orientation !== 'undefined'; var hasHiddenFocus = false; + const inputParent = + document.fullscreenElement || document.webkitFullscreenElement || document.body; if ( isMobile && !isChromeMobile && @@ -782,17 +784,17 @@ import * as PIXI from 'pixi.js'; input.type = 'text'; input.style.opacity = 0; input.style.position = 'absolute'; - input.style.left = `${self._x + - self._extraX + - (self._canvas ? self._canvas.offsetLeft : 0)}px`; - input.style.top = `${self._y + - self._extraY + - (self._canvas ? self._canvas.offsetTop : 0)}px`; + input.style.left = `${ + self._x + self._extraX + (self._canvas ? self._canvas.offsetLeft : 0) + }px`; + input.style.top = `${ + self._y + self._extraY + (self._canvas ? self._canvas.offsetTop : 0) + }px`; input.style.width = self._width; input.style.height = 0; const form = document.createElement('form'); form.appendChild(input); - document.body.appendChild(form); + inputParent.appendChild(form); input.focus(); input.addEventListener( 'blur', @@ -817,6 +819,9 @@ import * as PIXI from 'pixi.js'; // move the real focus to the hidden input const hasSelection = self._selection[0] > 0 || self._selection[1] > 0; hasHiddenFocus = true; + if (self._hiddenInput.parentNode !== inputParent) { + inputParent.appendChild(self._hiddenInput); + } self._hiddenInput.focus(); hasHiddenFocus = false; self._hiddenInput.selectionStart = hasSelection ? self._selection[0] : self._cursorPos; @@ -1284,7 +1289,7 @@ import * as PIXI from 'pixi.js'; } else { const img = new Image(); img.src = self._backgroundImage; - img.onload = function() { + img.onload = function () { ctx.drawImage( img, 0, diff --git a/src/class/stage.js b/src/class/stage.js index 72141bdc38..d00d213332 100644 --- a/src/class/stage.js +++ b/src/class/stage.js @@ -580,6 +580,8 @@ Entry.Stage = class Stage { this.inputField.show(); this.canvas.addChild(this.inputSubmitButton); + this._bindInputFieldFullScreenChange(); + Entry.requestUpdateTwice = true; function _createInputField() { @@ -664,6 +666,32 @@ Entry.Stage = class Stage { Entry.requestUpdate = true; } + _bindInputFieldFullScreenChange() { + if (this._inputFieldFullScreenChangeBound) { + return; + } + this._inputFieldFullScreenChangeBound = true; + + const relocate = () => { + const inputField = this.inputField; + const hiddenInput = inputField && inputField._hiddenInput; + if (!hiddenInput) { + return; + } + const inputParent = + document.fullscreenElement || document.webkitFullscreenElement || document.body; + if (hiddenInput.parentNode !== inputParent) { + inputParent.appendChild(hiddenInput); + if (inputField._hasFocus) { + hiddenInput.focus(); + } + } + }; + + document.addEventListener('fullscreenchange', relocate); + document.addEventListener('webkitfullscreenchange', relocate); + } + /** * init object containers */ From ba4acee8861a111c3472f61d22477a934a8719e4 Mon Sep 17 00:00:00 2001 From: JY Kim Date: Thu, 25 Jun 2026 15:31:15 +0900 Subject: [PATCH 3/3] Update src/util/utils.js Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/util/utils.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/util/utils.js b/src/util/utils.js index 7073ebfde1..b2e6176d62 100644 --- a/src/util/utils.js +++ b/src/util/utils.js @@ -486,8 +486,7 @@ Entry.Utils.generateId = function () { * @param {Function} callback 리스트를 갱신하는 동기 콜백 */ Entry.Utils.runWithScrollPreserved = function (containerEl, callback) { - const getScrollEl = () => - containerEl && containerEl.querySelector('.rcs-inner-container'); + const getScrollEl = () => containerEl && containerEl.querySelector('.rcs-inner-container'); const before = getScrollEl(); const top = before ? before.scrollTop : 0; const left = before ? before.scrollLeft : 0;