estreui 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -125,9 +125,11 @@ EstreUI pages have a distinct lifecycle, similar to Android Activities:
125
125
  1. **onBring**: Page is being prepared. Since the Active Struct hasn't been called yet, this is a good place for element creation tasks involving handles.
126
126
  2. **onOpen**: Page is opening (transition start). Called only once.
127
127
  3. **onShow**: Page is fully visible.
128
- 4. **onHide**: Page is hidden (covered by another page or closed).
129
- 5. **onClose**: Page is closed. Called only once.
130
- 6. **onRelease**: Page resources are released.
128
+ 4. **onFocus**: Page takes active focus (after `onShow`, and on window/tab return). Second arg `isFirstFocus` is true only on the first focus of the instance. Return `true` to opt out of the default in-page autoFocus.
129
+ 5. **onBlur**: Page loses active focus (before `onHide`, and on window/tab leave). `handle.isClosing` indicates the final blur along a close path.
130
+ 6. **onHide**: Page is hidden (covered by another page or closed).
131
+ 7. **onClose**: Page is closed. Called only once.
132
+ 8. **onRelease**: Page resources are released.
131
133
 
132
134
  * **onBack**: Called when back navigation is triggered. Return `true` to cancel the default action.
133
135
  * **onReload**: Called when page reload is triggered. Return `true` to cancel the default action (which is closing and reopening the page).
@@ -394,9 +396,11 @@ EstreUI 페이지는 Android Activity와 유사한 뚜렷한 라이프사이클
394
396
  1. **onBring**: 페이지가 준비되는 중입니다. Active struct가 호출되기 이전이므로 각종 handle 등을 포함하는 element 생성 작업을 할 수 있습니다.
395
397
  2. **onOpen**: 페이지가 열리는 중입니다 (전환 시작). 1회만 호출됩니다.
396
398
  3. **onShow**: 페이지가 완전히 보입니다.
397
- 4. **onHide**: 페이지가 숨겨졌습니다 (다른 페이지에 가려지거나 닫힘).
398
- 5. **onClose**: 페이지가 닫혔습니다. 1회만 호출됩니다.
399
- 6. **onRelease**: 페이지 리소스가 해제됩니다.
399
+ 4. **onFocus**: 페이지가 활성 포커스를 받습니다 (`onShow` 이후, 그리고 창/탭 복귀 시). 두 번째 인자 `isFirstFocus` 는 이 인스턴스의 최초 포커스일 때만 `true`. `true` 를 반환하면 기본 autoFocus(페이지 내 DOM 포커스 이동)를 스킵합니다.
400
+ 5. **onBlur**: 페이지가 포커스를 잃습니다 (`onHide` 직전, 그리고 창/탭 이탈 시). 닫힘 경로상 최종 blur 여부는 `handle.isClosing` 으로 식별합니다.
401
+ 6. **onHide**: 페이지가 숨겨졌습니다 (다른 페이지에 가려지거나 닫힘).
402
+ 7. **onClose**: 페이지가 닫혔습니다. 1회만 호출됩니다.
403
+ 8. **onRelease**: 페이지 리소스가 해제됩니다.
400
404
 
401
405
  * **onBack**: Back navigation이 호출될 때 실행되며, true를 반환할 경우 기본 작동이 취소됩니다.
402
406
  * **onReload**: 페이지를 새로고침하려고 할 때 호출됩니다. true를 반환할 경우 기본 작동이 취소됩니다. 기본 작동은 해당 페이지를 닫은 후 다시 열어주는 것입니다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "estreui",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "EstreUI Core Library - A comprehensive UI framework for web applications",
5
5
  "main": "scripts/estreUi.js",
6
6
  "files": [
@@ -387,6 +387,27 @@ const estreUi = {
387
387
  this.onBlur();
388
388
  });
389
389
 
390
+ // C (roadmap #006) — visibilitychange routes to onFocus/onBlur.
391
+ // More reliable on mobile browsers than window focus/blur, especially
392
+ // on Android WebView where native focus changes may not surface as JS events.
393
+ // Idempotent via the pageHandle.isFocused guard, so duplication with
394
+ // window focus/blur is harmless.
395
+ document.addEventListener("visibilitychange", () => {
396
+ if (window.isDebug) console.log(`[visibilitychange] state=${document.visibilityState} hasFocus=${document.hasFocus()}`);
397
+ if (document.visibilityState === "visible") this.onFocus();
398
+ else this.onBlur();
399
+ });
400
+
401
+ // A (roadmap #006) — track lastFocusedElement on the topmost showing handle.
402
+ // focusin bubbles (unlike blur), so a single document-level capture covers
403
+ // every page. Used by phase B's autoFocus to restore the prior focus point.
404
+ document.addEventListener("focusin", (e) => {
405
+ const topHandle = this.showingTopArticle ?? this.mainCurrentOnTop;
406
+ if (topHandle != null && topHandle.host?.contains(e.target)) {
407
+ topHandle.lastFocusedElement = e.target;
408
+ }
409
+ }, true);
410
+
390
411
  if (setOnReady) this.checkOnReady();
391
412
  });
392
413
  },
@@ -1575,12 +1596,15 @@ const estreUi = {
1575
1596
 
1576
1597
 
1577
1598
  async onFocus() {
1578
- // <= to do implement
1579
- // this.focus();
1599
+ const top = this.showingTopArticle ?? this.mainCurrentOnTop;
1600
+ if (window.isDebug) console.log(`[estreUi.onFocus] visibility=${document.visibilityState} hasFocus=${document.hasFocus()} top=${top?.pid ?? "(none)"}`);
1601
+ top?.focus();
1580
1602
  },
1581
-
1603
+
1582
1604
  async onBlur() {
1583
- // <= to do implement
1605
+ const top = this.showingTopArticle ?? this.mainCurrentOnTop;
1606
+ if (window.isDebug) console.log(`[estreUi.onBlur] visibility=${document.visibilityState} hasFocus=${document.hasFocus()} top=${top?.pid ?? "(none)"}`);
1607
+ await top?.blur();
1584
1608
  },
1585
1609
 
1586
1610
 
@@ -521,6 +521,48 @@ class EstreUiPageManager {
521
521
  });
522
522
  });
523
523
  }
524
+
525
+ /**
526
+ * Default auto-focus policy for a newly-focused page handle.
527
+ * Priority:
528
+ * 1. On repeat focus, restore `handle.lastFocusedElement` if still in the DOM.
529
+ * 2. First `[data-autofocus]` element inside the host.
530
+ * 3. First tab-reachable focusable element inside the host.
531
+ * 4. Otherwise no-op.
532
+ * Invoked from `pageHandle.onFocus()` when `handler.onFocus` does not return true.
533
+ * Projects can override on an `EstreUiCustomPageManager` subclass if a different policy is needed.
534
+ * @param {EstrePageHandle} handle - The page handle receiving focus.
535
+ * @param {boolean} isFirstFocus - True on the first focus after onOpen; false on subsequent focuses.
536
+ * @returns {boolean} Whether a focus() call succeeded.
537
+ */
538
+ autoFocus(handle, isFirstFocus) {
539
+ const host = handle?.host;
540
+ if (host == null) return false;
541
+
542
+ if (!isFirstFocus) {
543
+ const last = handle.lastFocusedElement;
544
+ if (last != null && host.contains(last) && document.body.contains(last)) {
545
+ last.focus();
546
+ if (document.activeElement === last) return true;
547
+ }
548
+ }
549
+
550
+ const markedTarget = host.querySelector("[data-autofocus]");
551
+ if (markedTarget != null && !markedTarget.hasAttribute("disabled") && !markedTarget.hidden) {
552
+ markedTarget.focus();
553
+ if (document.activeElement === markedTarget) return true;
554
+ }
555
+
556
+ const candidates = host.querySelectorAll(
557
+ 'input:not([disabled]),textarea:not([disabled]),select:not([disabled]),button:not([disabled]),[tabindex]:not([tabindex="-1"])'
558
+ );
559
+ for (const el of candidates) {
560
+ if (el.hidden) continue;
561
+ el.focus();
562
+ if (document.activeElement === el) return true;
563
+ }
564
+ return false;
565
+ }
524
566
  }
525
567
 
526
568
  const pageManager = new EstreUiPageManager();
@@ -99,6 +99,8 @@ class EstrePageHandle {
99
99
  get isShowing() { return this.isOpened && this.#isShowing; }
100
100
  #isFocused = false;
101
101
  get isFocused() { return this.isShowing && this.#isFocused; }
102
+ #everFocused = false;
103
+ get everFocused() { return this.#everFocused; }
102
104
 
103
105
  #isHiding = false;
104
106
  get isHiding() { return this.#isHiding; }
@@ -123,6 +125,8 @@ class EstrePageHandle {
123
125
  #isProcessing = f;
124
126
  get isProcessing() { return this.#isProcessing; }
125
127
 
128
+ lastFocusedElement = null;
129
+
126
130
  get mainArticle() { return this; }
127
131
 
128
132
 
@@ -381,7 +385,9 @@ class EstrePageHandle {
381
385
  const task = this.hide();
382
386
  return postAsyncQueue(async _ => {
383
387
  await task;
384
- return await this.onClose(isTermination, isOnRelease);
388
+ const result = await this.onClose(isTermination, isOnRelease);
389
+ this.#isClosing = false;
390
+ return result;
385
391
  });
386
392
  } else return false;
387
393
  }
@@ -416,10 +422,18 @@ class EstrePageHandle {
416
422
 
417
423
  onFocus() {
418
424
  if (!this.isFocused) {
425
+ const isFirstFocus = !this.#everFocused;
419
426
  if (window.isDebug) console.log("[onFocus] " + this.sectionBound + " " + this.hostType + " " + this.pid);//, this.host);
420
427
  this.#isFocused = true;
421
- if (this.handler?.onFocus != null) this.handler.onFocus(this);
428
+ this.#everFocused = true;
429
+ const handled = this.handler?.onFocus?.(this, isFirstFocus);
422
430
  if (this.intent?.onFocus != null) for (var item of this.intent.onFocus) if (item.from == this.hostType && !item.disabled) this.processAction(item);
431
+ if (handled === true) {
432
+ // Snapshot activeElement so a later refocus (e.g. background→foreground)
433
+ // can restore whatever the handler focused, even if focusin didn't record it.
434
+ const ae = document.activeElement;
435
+ if (ae != null && ae !== document.body && this.host?.contains(ae)) this.lastFocusedElement = ae;
436
+ } else pageManager.autoFocus?.(this, isFirstFocus);
423
437
  return true;
424
438
  } else return false;
425
439
  }
@@ -451,7 +465,7 @@ class EstrePageHandle {
451
465
  this.#isFocused = false;
452
466
  if (window.isDebug) console.log("[onBlur] " + this.sectionBound + " " + this.hostType + " " + this.pid);//, this.host);
453
467
  if (this.intent?.onBlur != null) for (var item of this.intent.onBlur) if (item.from == this.hostType && !item.disabled) await this.processAction(item);
454
- if (this.handler?.onBlur != null) await this.handler.onBlur(this);
468
+ await this.handler?.onBlur?.(this, this.isClosing);
455
469
  return true;
456
470
  } else return false;
457
471
  }
@@ -474,6 +488,7 @@ class EstrePageHandle {
474
488
  postQueue(_ => pageManager.bringPage(pid));
475
489
  }
476
490
  }
491
+ this.#isHiding = false;
477
492
  return true;
478
493
  } else return false;
479
494
  }
@@ -481,6 +496,8 @@ class EstrePageHandle {
481
496
  async onClose(isTermination = false, isOnRelease = false) {
482
497
  if (this.isOpened && (isOnRelease || !this.isStatic)) {
483
498
  this.#isOpened = false;
499
+ this.#everFocused = false;
500
+ this.lastFocusedElement = null;
484
501
  if (window.isDebug) console.log("[onClose] " + this.sectionBound + " " + this.hostType + " " + this.pid);//, this.host);
485
502
  if (this.intent?.onClose != null) for (var item of this.intent.onClose) if (item.from == this.hostType && !item.disabled) await this.processAction(item);
486
503
  if (this.handler?.onClose != null) await this.handler.onClose(this);
@@ -2975,6 +2992,7 @@ class EstreAlertDialogPageHandler extends EstreDialogPageHandler {
2975
2992
 
2976
2993
  onFocus(handle) {
2977
2994
  this.$confirm.focus();
2995
+ return true;
2978
2996
  }
2979
2997
  }
2980
2998
 
@@ -3022,6 +3040,7 @@ class EstreConfirmDialogPageHandler extends EstreDialogPageHandler {
3022
3040
 
3023
3041
  onFocus(handle) {
3024
3042
  this.$negative.focus();
3043
+ return true;
3025
3044
  }
3026
3045
  }
3027
3046
 
@@ -3081,6 +3100,7 @@ class EstrePromptDialogPageHandler extends EstreDialogPageHandler {
3081
3100
 
3082
3101
  onFocus(handle) {
3083
3102
  this.$input.focus();
3103
+ return true;
3084
3104
  }
3085
3105
  }
3086
3106
 
@@ -3110,6 +3130,7 @@ class EstreOptionDialogPageHandler extends EstreDialogPageHandler {
3110
3130
 
3111
3131
  onFocus(handle) {
3112
3132
  this.$optionItems[0]?.focus();
3133
+ return true;
3113
3134
  }
3114
3135
  }
3115
3136
 
@@ -3213,6 +3234,7 @@ class EstreSelectionDialogPageHandler extends EstreDialogPageHandler {
3213
3234
 
3214
3235
  onFocus(handle) {
3215
3236
  this.$confirm.focus();
3237
+ return true;
3216
3238
  }
3217
3239
 
3218
3240
  checkValidSelectAction(handle, handler, index, value, checked) {
@@ -3291,6 +3313,7 @@ class EstreDialsDialogPageHandler extends EstreDialogPageHandler {
3291
3313
 
3292
3314
  onFocus(handle) {
3293
3315
  this.$confirm.focus();
3316
+ return true;
3294
3317
  }
3295
3318
 
3296
3319
  onClose(handle) {
package/serviceWorker.js CHANGED
@@ -1,4 +1,4 @@
1
- const INSTALLATION_VERSION_NAME = "1.2.4-r20260418";
1
+ const INSTALLATION_VERSION_NAME = "1.2.6-r20260422";
2
2
  // ^^ Use for check new update "Native application(webview) version(or Android/iOS version combo) - PWA release version"
3
3
  // ex) "1.0.1/1.0.0-r20251101k"
4
4
 
@@ -21,7 +21,7 @@ const INSTALLATION_FILE_LIST = [
21
21
 
22
22
 
23
23
  // Common files cache - Be changes some time but, well not changed very often
24
- const CACHE_NAME_COMMON_FILES = "common-files-cache-v1-20260418";
24
+ const CACHE_NAME_COMMON_FILES = "common-files-cache-v1-20260422";
25
25
 
26
26
  const COMMON_FILES_TO_CACHE = [
27
27
  "./",