@eintrek/erp-shell 0.1.76 → 0.1.77
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 +27 -0
- package/dist/chunks/{format-BVoVc7Yk.cjs → format-DQLPHq7h.cjs} +223 -33
- package/dist/chunks/format-DQLPHq7h.cjs.map +1 -0
- package/dist/chunks/{format-BWUKNKxg.esm.js → format-ILrTMV4G.esm.js} +223 -33
- package/dist/chunks/format-ILrTMV4G.esm.js.map +1 -0
- package/dist/guide/guide-ui.d.ts +6 -0
- package/dist/guide/machine.d.ts +39 -0
- package/dist/guide/types.d.ts +6 -0
- package/dist/guide-core.cjs +1 -1
- package/dist/guide-core.esm.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.esm.js +3 -3
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunks/format-BVoVc7Yk.cjs.map +0 -1
- package/dist/chunks/format-BWUKNKxg.esm.js.map +0 -1
|
@@ -992,6 +992,7 @@ function initialGuideState(audience, progress, pages) {
|
|
|
992
992
|
pageRun: null,
|
|
993
993
|
panelSection: "all",
|
|
994
994
|
autoPassed: null,
|
|
995
|
+
counter: null,
|
|
995
996
|
audience,
|
|
996
997
|
progress,
|
|
997
998
|
screen: { name: "idle" },
|
|
@@ -1370,7 +1371,7 @@ function announceStep(d, suite, env) {
|
|
|
1370
1371
|
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
|
|
1371
1372
|
const title = s.bridge ? COPY.bridgeTitle : plainTitle(resolveText(step.title, ctx));
|
|
1372
1373
|
const why = s.bridge ? COPY.bridgeWhy(suite.appName) : plainTitle(resolveText(step.why, ctx));
|
|
1373
|
-
const pos = stepPosition(mission, s.index, env, d.state.progress.context, autoPassedOf(d.state, mission.key));
|
|
1374
|
+
const pos = s.pos ?? stepPosition(mission, s.index, env, d.state.progress.context, autoPassedOf(d.state, mission.key));
|
|
1374
1375
|
d.effects.push({
|
|
1375
1376
|
type: "announce",
|
|
1376
1377
|
text: `ขั้นที่ ${pos.no} จาก ${pos.total}. ${title}${why ? `. ${why}` : ""}`,
|
|
@@ -1404,6 +1405,54 @@ function stepPosition(mission, index, env, context, autoPassed = []) {
|
|
|
1404
1405
|
});
|
|
1405
1406
|
return { no: Math.max(no, 1), total: Math.max(total, 1) };
|
|
1406
1407
|
}
|
|
1408
|
+
/**
|
|
1409
|
+
* The steps after `index` this person will see, as far as can be known from
|
|
1410
|
+
* here: on this page, everything is read (requires, skipIf, a click whose
|
|
1411
|
+
* outcome already holds); on another page, a step whose showing depends on
|
|
1412
|
+
* what is on that page (`skipIf`, `onMissing: "skip"`) is not counted until
|
|
1413
|
+
* its page is reached.
|
|
1414
|
+
*/
|
|
1415
|
+
function stepsAhead(mission, index, env, context) {
|
|
1416
|
+
let n = 0;
|
|
1417
|
+
for (let j = index + 1; j < mission.steps.length; j++) {
|
|
1418
|
+
const step = mission.steps[j];
|
|
1419
|
+
if (step.requires && !safe(() => env.can(step.requires), true))
|
|
1420
|
+
continue;
|
|
1421
|
+
const home = stepHomeRoute(mission.steps, j);
|
|
1422
|
+
const here = !home || !!matchRoute(home, env.pathname, env.search, env.code);
|
|
1423
|
+
if (here) {
|
|
1424
|
+
const ctx = guideContext(env, context, routeParamsFor(step, env));
|
|
1425
|
+
if (step.skipIf && safe(() => step.skipIf(ctx), false))
|
|
1426
|
+
continue;
|
|
1427
|
+
if (wouldAutoPass(step, env))
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
else if (step.skipIf || step.onMissing === "skip") {
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
n++;
|
|
1434
|
+
}
|
|
1435
|
+
return n;
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* The counter as shown when entering step `index` (shell ≥ 0.1.77). `no`
|
|
1439
|
+
* counts the steps actually shown in this run up to this one. The total is
|
|
1440
|
+
* worked out on entering a step on a new page — the shown steps so far plus
|
|
1441
|
+
* stepsAhead — and kept while the run stays on that page; it never goes
|
|
1442
|
+
* down (the larger of the old and the new), and is never below `no`.
|
|
1443
|
+
*/
|
|
1444
|
+
function countStep(d, env, mission, index) {
|
|
1445
|
+
const prev = d.state.counter?.mission === mission.key ? d.state.counter : null;
|
|
1446
|
+
const shown = prev ? (prev.shown.includes(index) ? prev.shown : [...prev.shown, index]) : [index];
|
|
1447
|
+
const no = Math.max(1, shown.filter(i => i <= index).length);
|
|
1448
|
+
let total;
|
|
1449
|
+
if (prev && prev.path === env.pathname)
|
|
1450
|
+
total = Math.max(prev.total, no);
|
|
1451
|
+
else
|
|
1452
|
+
total = Math.max(prev?.total ?? 0, no + stepsAhead(mission, index, env, d.state.progress.context), no);
|
|
1453
|
+
d.state = { ...d.state, counter: { mission: mission.key, shown, total, path: env.pathname } };
|
|
1454
|
+
return { no, total };
|
|
1455
|
+
}
|
|
1407
1456
|
/** A step entered now would pass at once: a click or select step whose route or visible outcome holds. */
|
|
1408
1457
|
function wouldAutoPass(step, env) {
|
|
1409
1458
|
const kind = stepKind(step);
|
|
@@ -1473,6 +1522,7 @@ function enterStep(d, suite, env, mission, index, opts) {
|
|
|
1473
1522
|
const away = !page && !section && !!where && !matchRoute(where, env.pathname, env.search, env.code);
|
|
1474
1523
|
const bridge = !away && needsBridge(step, env, ctx);
|
|
1475
1524
|
const nts = !away && !bridge && nothingToSaveHolds(step, env, ctx);
|
|
1525
|
+
const pos = countStep(d, env, mission, i);
|
|
1476
1526
|
setScreen(d, {
|
|
1477
1527
|
name: "step",
|
|
1478
1528
|
mission: mission.key,
|
|
@@ -1490,6 +1540,7 @@ function enterStep(d, suite, env, mission, index, opts) {
|
|
|
1490
1540
|
...(page ? { page } : {}),
|
|
1491
1541
|
...(section ? { section } : {}),
|
|
1492
1542
|
...(flow ? { flow } : {}),
|
|
1543
|
+
pos,
|
|
1493
1544
|
});
|
|
1494
1545
|
if (!opts.replay) {
|
|
1495
1546
|
const cp = checkpointAtOrBefore(steps, i);
|
|
@@ -1764,7 +1815,7 @@ function startMission(d, suite, env, key, replay) {
|
|
|
1764
1815
|
const isReplay = replay || entry?.status === "done";
|
|
1765
1816
|
const before = d.state.progress;
|
|
1766
1817
|
// A new run: what passed by itself is counted afresh.
|
|
1767
|
-
d.state = { ...d.state, panelOpen: false, justDone: null, autoPassed: null };
|
|
1818
|
+
d.state = { ...d.state, panelOpen: false, justDone: null, autoPassed: null, counter: null };
|
|
1768
1819
|
if (d.state.progress.welcome === "not_seen")
|
|
1769
1820
|
withProgress(d, { welcome: "started" });
|
|
1770
1821
|
if (!isReplay && d.state.progress.mission_key !== key)
|
|
@@ -1914,24 +1965,33 @@ function pageStillHere(suite, key, env) {
|
|
|
1914
1965
|
const page = findPage(suite, key);
|
|
1915
1966
|
return !!page && pageMatchScore(page, whereOf(env)) !== null;
|
|
1916
1967
|
}
|
|
1917
|
-
/**
|
|
1968
|
+
/**
|
|
1969
|
+
* Whether a row may be written now: none of this version yet, or — once —
|
|
1970
|
+
* a skipped one that the person has now finished (skipped → completed,
|
|
1971
|
+
* shell ≥ 0.1.77). A completed row is final; skipped again changes nothing.
|
|
1972
|
+
*/
|
|
1973
|
+
function rowWritable(row, version, status) {
|
|
1974
|
+
if (!row || row.version < version)
|
|
1975
|
+
return true;
|
|
1976
|
+
return row.version === version && row.status === "skipped" && status === "completed";
|
|
1977
|
+
}
|
|
1978
|
+
/** Whether a run should keep its row to write at its end: no row yet, or only a skipped one. */
|
|
1979
|
+
function rowStillDue(row, version) {
|
|
1980
|
+
return rowWritable(row, version, "completed");
|
|
1981
|
+
}
|
|
1982
|
+
/** Write a page guide's row (once per page and version; skipped → completed once): recorded here at once, saved by the host. */
|
|
1918
1983
|
function recordPage(d, suite, key, status) {
|
|
1919
1984
|
const page = findPage(suite, key);
|
|
1920
|
-
if (!page ||
|
|
1985
|
+
if (!page || !rowWritable(d.state.pages[key], page.version, status))
|
|
1921
1986
|
return;
|
|
1922
1987
|
const prev = d.state.pages[key];
|
|
1923
1988
|
d.state = { ...d.state, pages: { ...d.state.pages, [key]: { version: page.version, status, rev: prev?.rev ?? 0 } } };
|
|
1924
1989
|
d.effects.push({ type: "savePage", key, version: page.version, status });
|
|
1925
1990
|
}
|
|
1926
|
-
/** The section guide's row says this person has ended this version of it. */
|
|
1927
|
-
function sectionRecorded(state, section) {
|
|
1928
|
-
const row = state.sections?.[section.key];
|
|
1929
|
-
return !!row && row.version >= section.version;
|
|
1930
|
-
}
|
|
1931
1991
|
/** Write a section guide's row (once per section and version). */
|
|
1932
1992
|
function recordSection(d, suite, key, status) {
|
|
1933
1993
|
const section = findSection(suite, key);
|
|
1934
|
-
if (!section ||
|
|
1994
|
+
if (!section || !rowWritable(d.state.sections?.[key], section.version, status))
|
|
1935
1995
|
return;
|
|
1936
1996
|
const prev = d.state.sections?.[key];
|
|
1937
1997
|
d.state = { ...d.state, sections: { ...d.state.sections, [key]: { version: section.version, status, rev: prev?.rev ?? 0 } } };
|
|
@@ -1962,7 +2022,7 @@ function flowRecorded(state, flow) {
|
|
|
1962
2022
|
/** Write a flow's row (once per flow and version). */
|
|
1963
2023
|
function recordFlow(d, suite, key, status) {
|
|
1964
2024
|
const flow = findFlow(suite, key);
|
|
1965
|
-
if (!flow ||
|
|
2025
|
+
if (!flow || !rowWritable(d.state.flows?.[key], flow.version, status))
|
|
1966
2026
|
return;
|
|
1967
2027
|
const prev = d.state.flows?.[key];
|
|
1968
2028
|
d.state = { ...d.state, flows: { ...d.state.flows, [key]: { version: flow.version, status, rev: prev?.rev ?? 0 } } };
|
|
@@ -2047,6 +2107,7 @@ function startFlow(d, suite, env, key, write, at) {
|
|
|
2047
2107
|
justDone: null,
|
|
2048
2108
|
pageRun: { key, write, kind: "flow", context: saved },
|
|
2049
2109
|
autoPassed: null,
|
|
2110
|
+
counter: null,
|
|
2050
2111
|
replayPage: {
|
|
2051
2112
|
mission: mission.key,
|
|
2052
2113
|
anchors: abortAnchorsOf(mission).filter(a => safe(() => env.isVisible(a), false)),
|
|
@@ -2077,6 +2138,7 @@ function startSection(d, suite, env, key, write) {
|
|
|
2077
2138
|
justDone: null,
|
|
2078
2139
|
pageRun: { key, write, kind: "section" },
|
|
2079
2140
|
autoPassed: null,
|
|
2141
|
+
counter: null,
|
|
2080
2142
|
replayPage: {
|
|
2081
2143
|
mission: mission.key,
|
|
2082
2144
|
anchors: abortAnchorsOf(mission).filter(a => safe(() => env.isVisible(a), false)),
|
|
@@ -2096,6 +2158,7 @@ function startPage(d, suite, env, key, write) {
|
|
|
2096
2158
|
justDone: null,
|
|
2097
2159
|
pageRun: { key, write, kind: "page" },
|
|
2098
2160
|
autoPassed: null,
|
|
2161
|
+
counter: null,
|
|
2099
2162
|
// What is open now is the person's: ending the guide leaves it alone.
|
|
2100
2163
|
replayPage: {
|
|
2101
2164
|
mission: mission.key,
|
|
@@ -2139,6 +2202,28 @@ function backTarget(mission, index, env, context) {
|
|
|
2139
2202
|
}
|
|
2140
2203
|
return prev >= 0 ? { index: prev, close: false } : null;
|
|
2141
2204
|
}
|
|
2205
|
+
/**
|
|
2206
|
+
* A flow's next step (the next one this person sees) when the person is on
|
|
2207
|
+
* its page and no longer on the current step's: they moved on by
|
|
2208
|
+
* themselves. -1 otherwise (still on this step's page, or elsewhere).
|
|
2209
|
+
*/
|
|
2210
|
+
function flowStepArrivedAt(mission, index, env, context) {
|
|
2211
|
+
const here = (route) => !!route && !!matchRoute(route, env.pathname, env.search, env.code);
|
|
2212
|
+
const curHome = stepHomeRoute(mission.steps, index);
|
|
2213
|
+
if (!curHome || here(curHome))
|
|
2214
|
+
return -1;
|
|
2215
|
+
for (let j = index + 1; j < mission.steps.length; j++) {
|
|
2216
|
+
const st = mission.steps[j];
|
|
2217
|
+
const ctx = guideContext(env, context, routeParamsFor(st, env));
|
|
2218
|
+
if (st.requires && !safe(() => env.can(st.requires), true))
|
|
2219
|
+
continue;
|
|
2220
|
+
if (st.skipIf && safe(() => st.skipIf(ctx), false))
|
|
2221
|
+
continue;
|
|
2222
|
+
const where = st.route ?? stepHomeRoute(mission.steps, j);
|
|
2223
|
+
return here(where) ? j : -1;
|
|
2224
|
+
}
|
|
2225
|
+
return -1;
|
|
2226
|
+
}
|
|
2142
2227
|
function reduceGuide(state, action, suite, env) {
|
|
2143
2228
|
const d = { state, effects: [] };
|
|
2144
2229
|
const s = state.screen;
|
|
@@ -2216,7 +2301,7 @@ function reduceGuide(state, action, suite, env) {
|
|
|
2216
2301
|
const stepAt = mission.steps.findIndex(st => st.key === d.state.progress.step_key);
|
|
2217
2302
|
const cpAt = mission.steps.findIndex(st => st.key === d.state.progress.checkpoint_key);
|
|
2218
2303
|
const index = stepAt >= 0 ? checkpointAtOrBefore(mission.steps, stepAt) : Math.max(cpAt, 0);
|
|
2219
|
-
d.state = { ...d.state, autoPassed: null };
|
|
2304
|
+
d.state = { ...d.state, autoPassed: null, counter: null };
|
|
2220
2305
|
enterStep(d, suite, env, mission, index, { replay: false });
|
|
2221
2306
|
break;
|
|
2222
2307
|
}
|
|
@@ -2259,6 +2344,15 @@ function reduceGuide(state, action, suite, env) {
|
|
|
2259
2344
|
break;
|
|
2260
2345
|
const step = mission.steps[s.index];
|
|
2261
2346
|
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
|
|
2347
|
+
if (s.flow && !s.confirmExit) {
|
|
2348
|
+
// Within the chain, the person went on to the next step's page by
|
|
2349
|
+
// themselves: carry on there, no "ทำต่อ" (shell ≥ 0.1.77).
|
|
2350
|
+
const next = flowStepArrivedAt(mission, s.index, env, d.state.progress.context);
|
|
2351
|
+
if (next >= 0) {
|
|
2352
|
+
enterStep(d, suite, env, mission, next, { replay: s.replay });
|
|
2353
|
+
break;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2262
2356
|
if (s.phase === "away") {
|
|
2263
2357
|
const where = placeRoute(mission.steps, s.index);
|
|
2264
2358
|
if (!where || matchRoute(where, env.pathname, env.search, env.code)) {
|
|
@@ -2653,7 +2747,7 @@ function reduceGuide(state, action, suite, env) {
|
|
|
2653
2747
|
if (!flowsAvailable(suite, env).includes(action.flow))
|
|
2654
2748
|
break;
|
|
2655
2749
|
const flow = findFlow(suite, action.flow);
|
|
2656
|
-
startFlow(d, suite, env, flow.key,
|
|
2750
|
+
startFlow(d, suite, env, flow.key, rowStillDue(state.flows?.[flow.key], flow.version));
|
|
2657
2751
|
break;
|
|
2658
2752
|
}
|
|
2659
2753
|
case "resumeFlow": {
|
|
@@ -2662,7 +2756,7 @@ function reduceGuide(state, action, suite, env) {
|
|
|
2662
2756
|
const flow = findFlow(suite, action.flow);
|
|
2663
2757
|
if (!flow || !flowShown(flow, guideContext(env, {})) || !inFlowChain(flow, whereOf(env)))
|
|
2664
2758
|
break;
|
|
2665
|
-
startFlow(d, suite, env, flow.key, action.write &&
|
|
2759
|
+
startFlow(d, suite, env, flow.key, action.write && rowStillDue(state.flows?.[flow.key], flow.version), { step: action.step, context: action.context });
|
|
2666
2760
|
break;
|
|
2667
2761
|
}
|
|
2668
2762
|
case "startPage": {
|
|
@@ -2674,7 +2768,7 @@ function reduceGuide(state, action, suite, env) {
|
|
|
2674
2768
|
const page = pageHere(suite, env);
|
|
2675
2769
|
if (!page || page.key !== action.page)
|
|
2676
2770
|
break;
|
|
2677
|
-
startPage(d, suite, env, page.key,
|
|
2771
|
+
startPage(d, suite, env, page.key, rowStillDue(state.pages[page.key], page.version));
|
|
2678
2772
|
break;
|
|
2679
2773
|
}
|
|
2680
2774
|
case "back": {
|
|
@@ -2723,7 +2817,7 @@ function reduceGuide(state, action, suite, env) {
|
|
|
2723
2817
|
if (!sectionsAvailable(suite, env).includes(action.section))
|
|
2724
2818
|
break;
|
|
2725
2819
|
const section = findSection(suite, action.section);
|
|
2726
|
-
startSection(d, suite, env, section.key,
|
|
2820
|
+
startSection(d, suite, env, section.key, rowStillDue(state.sections?.[section.key], section.version));
|
|
2727
2821
|
break;
|
|
2728
2822
|
}
|
|
2729
2823
|
case "idleCelebrate": {
|
|
@@ -3169,7 +3263,10 @@ async function storePageRow(adapter, app, key, version, status, rev, kind = "pag
|
|
|
3169
3263
|
}
|
|
3170
3264
|
if (current.version > version)
|
|
3171
3265
|
return null;
|
|
3172
|
-
|
|
3266
|
+
// This version already recorded: kept, except a skipped row this run
|
|
3267
|
+
// finished (skipped → completed, once, shell ≥ 0.1.77).
|
|
3268
|
+
const finishing = status === "completed" && current.status === "skipped";
|
|
3269
|
+
if (current.version === version && current.status !== "in_progress" && !finishing)
|
|
3173
3270
|
return current;
|
|
3174
3271
|
at = current.rev;
|
|
3175
3272
|
}
|
|
@@ -3836,6 +3933,9 @@ function useNudge(nudge, ref) {
|
|
|
3836
3933
|
}
|
|
3837
3934
|
function StepLayer(p) {
|
|
3838
3935
|
const { spot, phone, viewport } = p;
|
|
3936
|
+
// Where the bubble may sit: the content column when the app has one.
|
|
3937
|
+
const col = p.bounds && p.bounds.width > 0 ? p.bounds : { x: 0, width: viewport.width || (typeof window !== "undefined" ? window.innerWidth : 0)};
|
|
3938
|
+
const colKey = `${col.x}|${col.width}`;
|
|
3839
3939
|
const bubbleRef = useRef(null);
|
|
3840
3940
|
const arrowRef = useRef(null);
|
|
3841
3941
|
const ringRef = useRef(null);
|
|
@@ -3876,8 +3976,8 @@ function StepLayer(p) {
|
|
|
3876
3976
|
const bw = el.offsetWidth;
|
|
3877
3977
|
const bh = el.offsetHeight;
|
|
3878
3978
|
setPos(phone
|
|
3879
|
-
? { left: m, top: viewport.offsetTop + viewport.height - bh - m, side: "dock" }
|
|
3880
|
-
: { left: (
|
|
3979
|
+
? { left: col.x + m, top: viewport.offsetTop + viewport.height - bh - m, side: "dock" }
|
|
3980
|
+
: { left: col.x + (col.width - bw) / 2, top: viewport.offsetTop + (viewport.height - bh) / 2, side: "dock" });
|
|
3881
3981
|
return;
|
|
3882
3982
|
}
|
|
3883
3983
|
if (phone) {
|
|
@@ -3898,7 +3998,7 @@ function StepLayer(p) {
|
|
|
3898
3998
|
: overlapBot <= overlapTop
|
|
3899
3999
|
? botT
|
|
3900
4000
|
: topT;
|
|
3901
|
-
setPos({ left: m, top, side: "dock" });
|
|
4001
|
+
setPos({ left: col.x + m, top, side: "dock" });
|
|
3902
4002
|
return;
|
|
3903
4003
|
}
|
|
3904
4004
|
let cancelled = false;
|
|
@@ -3920,7 +4020,7 @@ function StepLayer(p) {
|
|
|
3920
4020
|
placement,
|
|
3921
4021
|
middleware: [
|
|
3922
4022
|
offset(placement.startsWith("bottom") ? gap : 14),
|
|
3923
|
-
shift({ padding: m }),
|
|
4023
|
+
shift({ padding: m, rootBoundary: { x: col.x, y: 0, width: col.width, height: window.innerHeight } }),
|
|
3924
4024
|
...(arrowRef.current ? [arrow({ element: arrowRef.current, padding: 14 })] : []),
|
|
3925
4025
|
],
|
|
3926
4026
|
});
|
|
@@ -3930,11 +4030,11 @@ function StepLayer(p) {
|
|
|
3930
4030
|
return;
|
|
3931
4031
|
const bw = el.offsetWidth;
|
|
3932
4032
|
const bh = el.offsetHeight;
|
|
3933
|
-
const bounds = { x: m, y: viewport.offsetTop + m, width:
|
|
4033
|
+
const bounds = { x: col.x + m, y: viewport.offsetTop + m, width: col.width - 2 * m, height: viewport.height - 2 * m };
|
|
3934
4034
|
const i = pickPlacement(results, bw, bh, a, clear, bounds);
|
|
3935
4035
|
if (i < 0) {
|
|
3936
4036
|
// Nowhere beside it: the bottom-right corner.
|
|
3937
|
-
setPos({ left:
|
|
4037
|
+
setPos({ left: col.x + col.width - bw - m, top: viewport.height + viewport.offsetTop - bh - m, side: "dock" });
|
|
3938
4038
|
return;
|
|
3939
4039
|
}
|
|
3940
4040
|
const { x, y, placement, middlewareData } = results[i];
|
|
@@ -3950,7 +4050,7 @@ function StepLayer(p) {
|
|
|
3950
4050
|
};
|
|
3951
4051
|
// The keys stand for spot, the lists, the holes and the bubble's size.
|
|
3952
4052
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3953
|
-
}, [spotKey, anchorKey, clearKey, contentKey, phone, showBubble, gap, viewport.height, viewport.offsetTop]);
|
|
4053
|
+
}, [spotKey, anchorKey, clearKey, contentKey, phone, showBubble, gap, viewport.height, viewport.offsetTop, colKey]);
|
|
3954
4054
|
const popovers = p.popovers ?? [];
|
|
3955
4055
|
const covered = !!p.covered;
|
|
3956
4056
|
const holes = [...(spot && !covered ? [spot] : []), ...popovers, ...p.holes];
|
|
@@ -3971,7 +4071,7 @@ function StepLayer(p) {
|
|
|
3971
4071
|
return (jsxs(Fragment, { children: [jsx(Scrim, { holes: p.phase === "ready" ? holes : [], open: p.phase === "ready" ? openHoles : [], width: viewport.width, height: Math.max(viewport.height + viewport.offsetTop, 0) }), spot && ringShown ? (jsx("div", { ref: ringRef, className: "eg-ring" + (p.moving ? " eg-move" : ""), style: { left: spot.x, top: spot.y, width: spot.width, height: spot.height }, "aria-hidden": "true" })) : null, hand ? (jsx("div", { className: "eg-hand" + (hand.flip ? " eg-flip" : ""), style: { left: hand.left, top: hand.top }, "aria-hidden": "true", children: jsx("svg", { viewBox: "0 0 24 24", children: jsx("path", { d: HAND_PATH }) }) })) : null, showBubble ? (jsxs("div", { ref: bubbleRef, className: "eg-bubble" + (p.bubble.error ? " eg-err" : ""), role: "dialog", "aria-modal": "false", "aria-labelledby": BUBBLE_TITLE_ID, "aria-describedby": BUBBLE_WHY_ID, tabIndex: -1, "data-eg-bubble": "", style: {
|
|
3972
4072
|
left: pos?.left ?? 0,
|
|
3973
4073
|
top: pos?.top ?? 0,
|
|
3974
|
-
width: phone ?
|
|
4074
|
+
width: phone ? col.width - 32 : col.width < 352 ? col.width - 32 : undefined,
|
|
3975
4075
|
visibility: pos ? "visible" : "hidden",
|
|
3976
4076
|
}, children: [jsx(BubbleBody, { ...p }), jsx("span", { ref: arrowRef, className: "eg-arrow" + (pos?.side === "bottom" ? " eg-top" : ""), style: arrowStyle ?? { display: "none" }, "aria-hidden": "true" })] })) : null, p.phase === "waiting" ? (jsxs("div", { className: "eg-card", role: "status", "data-eg-card": "", children: [jsxs("div", { className: "eg-loading", children: [jsx("span", { className: "eg-spin", "aria-hidden": "true" }), "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14\u2026"] }), jsx("div", { className: "eg-row", children: jsx("button", { type: "button", className: "eg-exit", onClick: p.onExit, children: exitLabelOf(p.bubble) }) })] })) : null, p.phase === "missing" ? (jsxs("div", { className: "eg-card eg-err", role: "alertdialog", "aria-labelledby": "eg-missing-t", "data-eg-card": "", tabIndex: -1, children: [jsx("p", { className: "eg-b-t", id: "eg-missing-t", children: "\u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E2A\u0E48\u0E27\u0E19\u0E19\u0E35\u0E49\u0E1A\u0E19\u0E2B\u0E19\u0E49\u0E32" }), jsxs("div", { className: "eg-row", children: [jsx("button", { type: "button", className: "eg-exit", onClick: p.onExit, children: exitLabelOf(p.bubble) }), jsx("button", { type: "button", className: "eg-btn eg-primary", onClick: p.onRetry, children: "\u0E25\u0E2D\u0E07\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07" })] })] })) : null, p.phase === "away" ? (jsxs("div", { className: "eg-card", role: "dialog", "aria-modal": "false", "aria-labelledby": "eg-away-t", "data-eg-card": "", tabIndex: -1, children: [jsxs("div", { className: "eg-b-top", children: [jsx("span", { className: "eg-mis", children: headingOf(p.bubble) }), jsxs("span", { className: "eg-cnt", children: ["\u0E02\u0E31\u0E49\u0E19\u0E17\u0E35\u0E48 ", p.bubble.index + 1, "/", p.bubble.total] })] }), jsx("p", { className: "eg-b-t", id: "eg-away-t", style: { marginTop: 8 }, children: jsx(GuideTitle, { text: p.bubble.title }) }), jsxs("div", { className: "eg-row", children: [jsx("button", { type: "button", className: "eg-exit", onClick: p.onExit, children: exitLabelOf(p.bubble) }), jsx("button", { type: "button", className: "eg-btn eg-primary", onClick: p.onGoResume, children: "\u0E17\u0E33\u0E15\u0E48\u0E2D" })] })] })) : null, p.confirmExit ? (jsx(ConfirmModal, { title: "\u0E2D\u0E2D\u0E01\u0E08\u0E32\u0E01\u0E44\u0E01\u0E14\u0E4C\u0E15\u0E2D\u0E19\u0E19\u0E35\u0E49?", line: p.reopenHint || REOPEN_HINT, leaveLabel: "\u0E2D\u0E2D\u0E01\u0E08\u0E32\u0E01\u0E44\u0E01\u0E14\u0E4C", stayLabel: "\u0E17\u0E33\u0E15\u0E48\u0E2D", onLeave: p.onExitConfirm, onStay: p.onExitCancel })) : null] }));
|
|
3977
4077
|
}
|
|
@@ -4339,6 +4439,42 @@ function runAbort(actions) {
|
|
|
4339
4439
|
}, ABORT_DELAY_MS + i * ABORT_GAP_MS);
|
|
4340
4440
|
});
|
|
4341
4441
|
}
|
|
4442
|
+
/** The app's content column (GuideConfig.contentRoot), cut to the viewport; null when none is declared or found. */
|
|
4443
|
+
function contentColumn(selector, vw) {
|
|
4444
|
+
if (!selector || typeof document === "undefined")
|
|
4445
|
+
return null;
|
|
4446
|
+
let el = null;
|
|
4447
|
+
try {
|
|
4448
|
+
el = document.querySelector(selector);
|
|
4449
|
+
}
|
|
4450
|
+
catch {
|
|
4451
|
+
return null;
|
|
4452
|
+
}
|
|
4453
|
+
if (!el)
|
|
4454
|
+
return null;
|
|
4455
|
+
const r = el.getBoundingClientRect();
|
|
4456
|
+
const x = Math.max(0, r.left);
|
|
4457
|
+
const right = Math.min(vw || window.innerWidth, r.right);
|
|
4458
|
+
if (right - x <= 0)
|
|
4459
|
+
return null;
|
|
4460
|
+
return { x, y: 0, width: right - x, height: 0 };
|
|
4461
|
+
}
|
|
4462
|
+
/** The control a form's implicit submit (Enter in a field) presses: its first submit button. */
|
|
4463
|
+
function defaultSubmitControl(form) {
|
|
4464
|
+
for (const el of Array.from(form.elements)) {
|
|
4465
|
+
if (el instanceof HTMLButtonElement && (el.type === "submit" || !el.getAttribute("type")))
|
|
4466
|
+
return el;
|
|
4467
|
+
if (el instanceof HTMLInputElement && (el.type === "submit" || el.type === "image"))
|
|
4468
|
+
return el;
|
|
4469
|
+
}
|
|
4470
|
+
return null;
|
|
4471
|
+
}
|
|
4472
|
+
/** A field where Enter submits its form (not a textarea, a button or a select). */
|
|
4473
|
+
function submitsOnEnter(el) {
|
|
4474
|
+
if (!(el instanceof HTMLInputElement))
|
|
4475
|
+
return false;
|
|
4476
|
+
return !["button", "submit", "reset", "image", "file", "hidden"].includes(el.type);
|
|
4477
|
+
}
|
|
4342
4478
|
/** A step's own target takes presses: not on a "view" step, unless the step allows it. */
|
|
4343
4479
|
function targetPressable(step, bridge) {
|
|
4344
4480
|
if (!step || bridge)
|
|
@@ -4717,6 +4853,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4717
4853
|
const stepId = screen.name === "step" ? `${screen.mission}:${screen.index}:${screen.bridge ? "b" : ""}${screen.nothingToSave ? "n" : ""}` : "";
|
|
4718
4854
|
/* Measure every frame while a step is shown: the spotlight follows
|
|
4719
4855
|
scrolling, dialogs animating in, the drawer sliding, the keyboard. */
|
|
4856
|
+
const contentRoot = config.contentRoot;
|
|
4720
4857
|
const [layout, setLayout] = useState({
|
|
4721
4858
|
spot: null,
|
|
4722
4859
|
covered: false,
|
|
@@ -4725,6 +4862,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4725
4862
|
vw: 0,
|
|
4726
4863
|
vh: 0,
|
|
4727
4864
|
oy: 0,
|
|
4865
|
+
col: null,
|
|
4728
4866
|
});
|
|
4729
4867
|
useEffect(() => {
|
|
4730
4868
|
if (!inStep) {
|
|
@@ -4733,7 +4871,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4733
4871
|
}
|
|
4734
4872
|
let raf = 0;
|
|
4735
4873
|
let lostSince = null;
|
|
4736
|
-
let last = { spot: null, covered: false, holes: [], popovers: [], vw: 0, vh: 0, oy: 0 };
|
|
4874
|
+
let last = { spot: null, covered: false, holes: [], popovers: [], vw: 0, vh: 0, oy: 0, col: null };
|
|
4737
4875
|
targetElRef.current = target ? findAnchor(target.anchor, target.id) : null;
|
|
4738
4876
|
const allow = stepInfo?.step.allow ?? [];
|
|
4739
4877
|
// The spotlight is the part of the target a person can see: cut by the
|
|
@@ -4793,13 +4931,15 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4793
4931
|
holes.push(padRect(rectOf(ael), TARGET_PAD));
|
|
4794
4932
|
}
|
|
4795
4933
|
const covered = !!spot && coveredByDialog(targetElRef.current);
|
|
4796
|
-
const
|
|
4934
|
+
const col = contentColumn(contentRoot, vw);
|
|
4935
|
+
const next = { spot, covered, holes, popovers, vw, vh, oy, col };
|
|
4797
4936
|
const sameList = (a, b) => a.length === b.length && a.every((h, i) => sameRect(h, b[i]));
|
|
4798
4937
|
if (!sameRect(next.spot, last.spot) ||
|
|
4799
4938
|
next.covered !== last.covered ||
|
|
4800
4939
|
next.vw !== last.vw ||
|
|
4801
4940
|
next.vh !== last.vh ||
|
|
4802
4941
|
next.oy !== last.oy ||
|
|
4942
|
+
!sameRect(next.col, last.col) ||
|
|
4803
4943
|
!sameList(next.holes, last.holes) ||
|
|
4804
4944
|
!sameList(next.popovers, last.popovers)) {
|
|
4805
4945
|
last = next;
|
|
@@ -4954,6 +5094,43 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4954
5094
|
}
|
|
4955
5095
|
return false;
|
|
4956
5096
|
};
|
|
5097
|
+
/**
|
|
5098
|
+
* Whether a form may be submitted during this step (shell ≥ 0.1.77): only
|
|
5099
|
+
* when its submit control — the button pressed, or the one Enter in a
|
|
5100
|
+
* field would press — is the step's pressable target or in its `allow`.
|
|
5101
|
+
* Otherwise Enter in an allowed field would save a draft (and take a
|
|
5102
|
+
* document number) behind the guide's back.
|
|
5103
|
+
*/
|
|
5104
|
+
const submitAllowed = (form, submitter) => {
|
|
5105
|
+
const btn = submitter ?? defaultSubmitControl(form);
|
|
5106
|
+
if (!btn)
|
|
5107
|
+
return false;
|
|
5108
|
+
const el = targetElRef.current;
|
|
5109
|
+
const sc = stateRef.current?.screen;
|
|
5110
|
+
const pressable = targetPressable(stepInfoRef.current?.step, sc?.name === "step" && sc.bridge);
|
|
5111
|
+
if (el && pressable && el.contains(btn))
|
|
5112
|
+
return true;
|
|
5113
|
+
for (const a of stepInfoRef.current?.step.allow ?? []) {
|
|
5114
|
+
const ael = findAnchor(a);
|
|
5115
|
+
if (ael && ael.contains(btn))
|
|
5116
|
+
return true;
|
|
5117
|
+
}
|
|
5118
|
+
return false;
|
|
5119
|
+
};
|
|
5120
|
+
const onSubmit = (e) => {
|
|
5121
|
+
const sc = stateRef.current?.screen;
|
|
5122
|
+
if (!guardingRef.current || sc?.name !== "step")
|
|
5123
|
+
return;
|
|
5124
|
+
const form = e.target;
|
|
5125
|
+
if (!(form instanceof HTMLFormElement) || inGuideLayer(form))
|
|
5126
|
+
return;
|
|
5127
|
+
const submitter = e.submitter ?? null;
|
|
5128
|
+
if (submitAllowed(form, submitter))
|
|
5129
|
+
return;
|
|
5130
|
+
e.preventDefault();
|
|
5131
|
+
e.stopPropagation();
|
|
5132
|
+
dispatchRef.current({ type: "blocked" });
|
|
5133
|
+
};
|
|
4957
5134
|
const onPointer = (e) => {
|
|
4958
5135
|
const t = e.target;
|
|
4959
5136
|
if (inGuideLayer(t)) {
|
|
@@ -5073,8 +5250,15 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5073
5250
|
return;
|
|
5074
5251
|
}
|
|
5075
5252
|
}
|
|
5076
|
-
if (modalRef.current || !allowedTarget(active))
|
|
5253
|
+
if (modalRef.current || !allowedTarget(active)) {
|
|
5077
5254
|
stop();
|
|
5255
|
+
return;
|
|
5256
|
+
}
|
|
5257
|
+
// Enter in a field the step lets the person type in: no implicit
|
|
5258
|
+
// submit unless the form's submit control is allowed now.
|
|
5259
|
+
if (e.key === "Enter" && sc.name === "step" && submitsOnEnter(active) && active.form && !submitAllowed(active.form, null)) {
|
|
5260
|
+
e.preventDefault();
|
|
5261
|
+
}
|
|
5078
5262
|
}
|
|
5079
5263
|
};
|
|
5080
5264
|
window.addEventListener("pointerdown", onPointer, true);
|
|
@@ -5084,7 +5268,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5084
5268
|
window.addEventListener("focusin", onFocusIn, true);
|
|
5085
5269
|
window.addEventListener("focusout", onFocusOut, true);
|
|
5086
5270
|
window.addEventListener("keydown", onKey, true);
|
|
5271
|
+
window.addEventListener("submit", onSubmit, true);
|
|
5087
5272
|
return () => {
|
|
5273
|
+
window.removeEventListener("submit", onSubmit, true);
|
|
5088
5274
|
window.removeEventListener("pointerdown", onPointer, true);
|
|
5089
5275
|
window.removeEventListener("mousedown", onPointer, true);
|
|
5090
5276
|
window.removeEventListener("touchstart", onPointer, true);
|
|
@@ -5512,7 +5698,11 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5512
5698
|
const missionNo = missionNumber(suite, mission.key, ctxIdle);
|
|
5513
5699
|
const kind = stepKind(step);
|
|
5514
5700
|
const bridge = screen.bridge;
|
|
5515
|
-
|
|
5701
|
+
// What is ahead now decides เสร็จ; the number shown is the run's counter,
|
|
5702
|
+
// which never goes down (0.1.77).
|
|
5703
|
+
const raw = stepPosition(mission, screen.index, env(), state.progress.context, autoPassedOf(state, mission.key));
|
|
5704
|
+
const last = raw.no >= raw.total;
|
|
5705
|
+
const pos = screen.pos ?? raw;
|
|
5516
5706
|
const page = screen.page ? findPage(suite, screen.page) : undefined;
|
|
5517
5707
|
const section = screen.section ? findSection(suite, screen.section) : undefined;
|
|
5518
5708
|
const flow = screen.flow ? findFlow(suite, screen.flow) : undefined;
|
|
@@ -5521,16 +5711,16 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5521
5711
|
? {
|
|
5522
5712
|
...read,
|
|
5523
5713
|
showBack: kind === "view" && !!backTarget(mission, screen.index, env(), state.progress.context),
|
|
5524
|
-
nextLabel:
|
|
5714
|
+
nextLabel: last ? "เสร็จ" : undefined,
|
|
5525
5715
|
}
|
|
5526
5716
|
: flow
|
|
5527
5717
|
? {
|
|
5528
5718
|
heading: `พาทำ · ${flow.title}`,
|
|
5529
5719
|
exitLabel: "ออกจากการพาทำ",
|
|
5530
|
-
nextLabel: kind === "view" &&
|
|
5720
|
+
nextLabel: kind === "view" && last ? "เสร็จ" : undefined,
|
|
5531
5721
|
}
|
|
5532
5722
|
: {};
|
|
5533
|
-
content = (jsx(StepLayer, { spot: layout.spot, holes: layout.holes, popovers: layout.popovers, covered: layout.covered, viewport: { width: layout.vw || window.innerWidth, height: layout.vh || window.innerHeight, offsetTop: layout.oy }, phone: phone, showHand: bridge || (kind !== "input" && kind !== "view"), bubble: {
|
|
5723
|
+
content = (jsx(StepLayer, { spot: layout.spot, holes: layout.holes, popovers: layout.popovers, covered: layout.covered, viewport: { width: layout.vw || window.innerWidth, height: layout.vh || window.innerHeight, offsetTop: layout.oy }, bounds: layout.col, phone: phone, showHand: bridge || (kind !== "input" && kind !== "view"), bubble: {
|
|
5534
5724
|
missionNo,
|
|
5535
5725
|
missionTitle: mission.title,
|
|
5536
5726
|
replay: screen.replay,
|
|
@@ -5713,4 +5903,4 @@ function thaiMonthYear(m, opts = {}) {
|
|
|
5713
5903
|
}
|
|
5714
5904
|
|
|
5715
5905
|
export { API_EVENT as A, useGuideEntry as B, usePageGuideEntry as C, usePageGuides as D, useSectionGuideValues as E, GUIDE_EVENT_PREFIX as G, SectionGuideButton as S, THAI_MONTHS_SHORT as T, GUIDE_LAYER_ATTRIBUTE as a, GuideErrorBoundary as b, GuideProgressConflict as c, GuideRuntime as d, apiCallDetail as e, emitGuideEvent as f, fillRoute as g, formatBaht as h, guideAnchor as i, guideMenuItems as j, guideMenuShown as k, loadGuides as l, matchApiPath as m, matchRoute as n, openFlow as o, openFlowsPanel as p, openGuidePanel as q, openPageGuide as r, openPagesPanel as s, openSectionGuide as t, reportApiCall as u, reportApiEvent as v, setSectionGuideValues as w, storeGuides as x, thaiMonth as y, thaiMonthYear as z };
|
|
5716
|
-
//# sourceMappingURL=format-
|
|
5906
|
+
//# sourceMappingURL=format-ILrTMV4G.esm.js.map
|