@eintrek/erp-shell 0.1.71 → 0.1.73
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 +22 -0
- package/dist/chunks/{guide-runtime-BTatpm-V.esm.js → guide-runtime-C7Z00QwM.esm.js} +103 -9
- package/dist/chunks/guide-runtime-C7Z00QwM.esm.js.map +1 -0
- package/dist/chunks/{guide-runtime-B_fYkQv7.cjs → guide-runtime-CLVSNkTN.cjs} +103 -8
- package/dist/chunks/guide-runtime-CLVSNkTN.cjs.map +1 -0
- package/dist/guide/core.d.ts +1 -1
- package/dist/guide/guide-store.d.ts +8 -0
- package/dist/guide/guide-styles.d.ts +1 -1
- package/dist/guide/guide-ui.d.ts +2 -0
- package/dist/guide/machine.d.ts +26 -0
- package/dist/guide/types.d.ts +8 -0
- package/dist/guide-core.cjs +2 -1
- package/dist/guide-core.cjs.map +1 -1
- package/dist/guide-core.esm.js +1 -1
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +5 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunks/guide-runtime-BTatpm-V.esm.js.map +0 -1
- package/dist/chunks/guide-runtime-B_fYkQv7.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -176,6 +176,28 @@ progress API: `API.md` next to it.
|
|
|
176
176
|
shell's adapters), `guide-ui.tsx`, `guide-styles.ts`. Tests:
|
|
177
177
|
`src/guide/*.test.ts`.
|
|
178
178
|
|
|
179
|
+
### 0.1.73
|
|
180
|
+
|
|
181
|
+
- `GuideStep.status`: a short status line in the bubble while it returns
|
|
182
|
+
text, read again on every page change. A select step names a wrong pick
|
|
183
|
+
("ยังไม่ใช่ชื่อคุณ") instead of waiting in silence (owner, 3 Nov 2026:
|
|
184
|
+
Plannex's managers step, someone else picked, nothing moved).
|
|
185
|
+
- In a replay (ทบทวน), a select step offers ถัดไป when the next step's target
|
|
186
|
+
is already on the page (a form's save button), so a review never has to
|
|
187
|
+
change a real record to go on. Not while the next target needs the pick
|
|
188
|
+
(Plannex's save shows only once the user is a manager). First runs are
|
|
189
|
+
unchanged.
|
|
190
|
+
- The "?" button opens the help dialog straight away, without the
|
|
191
|
+
"ไกด์เริ่มต้น 0/0" row, when every mission is hidden for this user
|
|
192
|
+
(`guideMenuShown`). `useGuideEntry()` still answers `{ done, total }`;
|
|
193
|
+
an app's own entry row should hide at `total === 0` too.
|
|
194
|
+
|
|
195
|
+
### 0.1.72
|
|
196
|
+
|
|
197
|
+
- "ขั้นที่ x/y" counts only the steps this person will see: a step whose
|
|
198
|
+
`requires` they lack or whose `skipIf` holds is left out (Plannex mission 3
|
|
199
|
+
said 1/6 and ended at 5/6).
|
|
200
|
+
|
|
179
201
|
### 0.1.71
|
|
180
202
|
|
|
181
203
|
- Escape on the finish card closes it where the person is. Only the button
|
|
@@ -337,6 +337,16 @@ function publishGuideEntry(next, openPanel) {
|
|
|
337
337
|
emit();
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Whether the "?" menu offers "ไกด์เริ่มต้น": a tour whose missions are all
|
|
342
|
+
* hidden for this user (`visible`, e.g. Plannex without an employee record)
|
|
343
|
+
* has nothing to open, and "0/0" led to an empty panel. The entry itself
|
|
344
|
+
* stays published: apps wait on it before they load the facts that make
|
|
345
|
+
* missions visible.
|
|
346
|
+
*/
|
|
347
|
+
function guideMenuShown(e) {
|
|
348
|
+
return !!e && e.total > 0;
|
|
349
|
+
}
|
|
340
350
|
/** Opens the tour's quest panel, when this user has a tour here. */
|
|
341
351
|
function openGuidePanel() {
|
|
342
352
|
openPanelImpl?.();
|
|
@@ -727,11 +737,34 @@ function announceStep(d, suite, env) {
|
|
|
727
737
|
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
|
|
728
738
|
const title = s.bridge ? COPY.bridgeTitle : plainTitle(resolveText(step.title, ctx));
|
|
729
739
|
const why = s.bridge ? COPY.bridgeWhy(suite.appName) : plainTitle(resolveText(step.why, ctx));
|
|
740
|
+
const pos = stepPosition(mission, s.index, env, d.state.progress.context);
|
|
730
741
|
d.effects.push({
|
|
731
742
|
type: "announce",
|
|
732
|
-
text: `ขั้นที่ ${
|
|
743
|
+
text: `ขั้นที่ ${pos.no} จาก ${pos.total}. ${title}${why ? `. ${why}` : ""}`,
|
|
733
744
|
});
|
|
734
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* "ขั้นที่ no จาก total" counting only the steps this person will see: a step
|
|
748
|
+
* whose `requires` they lack or whose `skipIf` holds is left out (the step on
|
|
749
|
+
* screen always counts).
|
|
750
|
+
*/
|
|
751
|
+
function stepPosition(mission, index, env, context) {
|
|
752
|
+
let no = 0;
|
|
753
|
+
let total = 0;
|
|
754
|
+
mission.steps.forEach((step, i) => {
|
|
755
|
+
if (i !== index) {
|
|
756
|
+
if (step.requires && !safe(() => env.can(step.requires), true))
|
|
757
|
+
return;
|
|
758
|
+
const ctx = guideContext(env, context, routeParamsFor(step, env));
|
|
759
|
+
if (step.skipIf && safe(() => step.skipIf(ctx), false))
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
total++;
|
|
763
|
+
if (i <= index)
|
|
764
|
+
no = total;
|
|
765
|
+
});
|
|
766
|
+
return { no: Math.max(no, 1), total: Math.max(total, 1) };
|
|
767
|
+
}
|
|
735
768
|
/** "กด [บันทึก]" → "กด บันทึก" (for screen readers). */
|
|
736
769
|
function plainTitle(t) {
|
|
737
770
|
return t.replace(/\[([^\]]+)\]/g, "$1");
|
|
@@ -780,6 +813,7 @@ function enterStep(d, suite, env, mission, index, opts) {
|
|
|
780
813
|
const where = placeRoute(steps, i);
|
|
781
814
|
const away = !!where && !matchRoute(where, env.pathname, env.search, env.code);
|
|
782
815
|
const bridge = !away && needsBridge(step, env, ctx);
|
|
816
|
+
const nts = !away && !bridge && nothingToSaveHolds(step, env, ctx);
|
|
783
817
|
setScreen(d, {
|
|
784
818
|
name: "step",
|
|
785
819
|
mission: mission.key,
|
|
@@ -791,7 +825,9 @@ function enterStep(d, suite, env, mission, index, opts) {
|
|
|
791
825
|
confirmExit: false,
|
|
792
826
|
misses: 0,
|
|
793
827
|
armed: hiddenAnchorShown(step, env),
|
|
794
|
-
nothingToSave:
|
|
828
|
+
nothingToSave: nts,
|
|
829
|
+
status: away || bridge ? null : stepStatus(shownStep(step, nts), ctx),
|
|
830
|
+
replayNext: !away && !bridge && opts.replay && replayCanGoOn(mission, i, env, d.state.progress.context, nts),
|
|
795
831
|
});
|
|
796
832
|
if (!opts.replay) {
|
|
797
833
|
const cp = checkpointAtOrBefore(steps, i);
|
|
@@ -834,6 +870,54 @@ function setNothingToSave(d, suite, env, on) {
|
|
|
834
870
|
if (stepTargetVisible(step, env, ctx, s.bridge))
|
|
835
871
|
announceStep(d, suite, env);
|
|
836
872
|
}
|
|
873
|
+
/** The step's status line (GuideStep.status): trimmed text, or null. */
|
|
874
|
+
function stepStatus(step, ctx) {
|
|
875
|
+
if (!step.status)
|
|
876
|
+
return null;
|
|
877
|
+
const text = safe(() => step.status(ctx), undefined);
|
|
878
|
+
return typeof text === "string" && text.trim() ? text.trim() : null;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* In a replay, whether a "select" step may go on with ถัดไป: the next step
|
|
882
|
+
* this person sees has its target on the page already (or there is none).
|
|
883
|
+
* A pick that makes the next step's target (a save shown only once the
|
|
884
|
+
* right value is chosen) is still needed.
|
|
885
|
+
*/
|
|
886
|
+
function replayCanGoOn(mission, index, env, context, nothingToSave = false) {
|
|
887
|
+
if (stepKind(shownStep(mission.steps[index], nothingToSave)) !== "select")
|
|
888
|
+
return false;
|
|
889
|
+
for (let i = index + 1; i < mission.steps.length; i++) {
|
|
890
|
+
const next = mission.steps[i];
|
|
891
|
+
const ctx = guideContext(env, context, routeParamsFor(next, env));
|
|
892
|
+
if (next.requires && !safe(() => env.can(next.requires), true))
|
|
893
|
+
continue;
|
|
894
|
+
if (next.skipIf && safe(() => next.skipIf(ctx), false))
|
|
895
|
+
continue;
|
|
896
|
+
if (!next.target)
|
|
897
|
+
return true;
|
|
898
|
+
if (stepTargetVisible(next, env, ctx, false))
|
|
899
|
+
return true;
|
|
900
|
+
// A commit step with nothing to save is read on its saved state.
|
|
901
|
+
return nothingToSaveHolds(next, env, ctx);
|
|
902
|
+
}
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
/** Keep the status line and a replay's ถัดไป current (the page changed). */
|
|
906
|
+
function syncStatus(d, suite, env) {
|
|
907
|
+
const s = d.state.screen;
|
|
908
|
+
if (s.name !== "step")
|
|
909
|
+
return;
|
|
910
|
+
const mission = findMission(suite, s.mission);
|
|
911
|
+
if (!mission)
|
|
912
|
+
return;
|
|
913
|
+
const step = shownStep(mission.steps[s.index], s.nothingToSave);
|
|
914
|
+
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
|
|
915
|
+
const off = s.phase === "away" || s.bridge;
|
|
916
|
+
const status = off ? null : stepStatus(step, ctx);
|
|
917
|
+
const replayNext = !off && s.replay && replayCanGoOn(mission, s.index, env, d.state.progress.context, s.nothingToSave);
|
|
918
|
+
if (status !== s.status || replayNext !== s.replayNext)
|
|
919
|
+
setScreen(d, { ...s, status, replayNext });
|
|
920
|
+
}
|
|
837
921
|
function markReadyIfVisible(d, suite, env) {
|
|
838
922
|
const s = d.state.screen;
|
|
839
923
|
if (s.name !== "step" || s.phase !== "waiting")
|
|
@@ -1219,6 +1303,7 @@ function reduceGuide(state, action, suite, env) {
|
|
|
1219
1303
|
else if (!cur.bridge && needsBridge(step, env, ctx)) {
|
|
1220
1304
|
setScreen(d, { ...cur, bridge: true, phase: "waiting" });
|
|
1221
1305
|
}
|
|
1306
|
+
syncStatus(d, suite, env);
|
|
1222
1307
|
markReadyIfVisible(d, suite, env);
|
|
1223
1308
|
break;
|
|
1224
1309
|
}
|
|
@@ -1281,6 +1366,12 @@ function reduceGuide(state, action, suite, env) {
|
|
|
1281
1366
|
advance(d, suite, env);
|
|
1282
1367
|
break;
|
|
1283
1368
|
}
|
|
1369
|
+
if (kind === "select") {
|
|
1370
|
+
// A replay goes on without a pick when the next step is on the page.
|
|
1371
|
+
if (s.replay && replayCanGoOn(mission, s.index, env, d.state.progress.context, s.nothingToSave))
|
|
1372
|
+
advance(d, suite, env);
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1284
1375
|
if (kind !== "input")
|
|
1285
1376
|
break;
|
|
1286
1377
|
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
|
|
@@ -2287,6 +2378,7 @@ const GUIDE_CSS = `
|
|
|
2287
2378
|
.eg-b-t{margin:0;font-size:16px;font-weight:600;line-height:1.45}
|
|
2288
2379
|
.eg-b-w{margin:2px 0 0;font-size:13.5px;color:var(--eg-muted)}
|
|
2289
2380
|
.eg-bubble.eg-err .eg-b-t{color:var(--eg-danger)}
|
|
2381
|
+
.eg-b-s{margin:6px 0 0;font-size:13px;font-weight:600;color:var(--eg-danger)}
|
|
2290
2382
|
.eg-k{display:inline-block;background:var(--eg-tint);color:var(--eg-strong);border-radius:7px;padding:0 7px;font-weight:600}
|
|
2291
2383
|
.eg-b-f{display:flex;align-items:center;justify-content:space-between;margin-top:12px;gap:8px}
|
|
2292
2384
|
.eg-b-f .eg-btn{height:34px;padding:0 14px;font-size:13.5px}
|
|
@@ -2520,7 +2612,7 @@ function StepLayer(p) {
|
|
|
2520
2612
|
const showBubble = p.phase === "ready" && (!!spot || !p.hasTarget);
|
|
2521
2613
|
const gap = p.showHand ? 34 : 14;
|
|
2522
2614
|
const spotKey = spot ? `${spot.x}|${spot.y}|${spot.width}|${spot.height}` : "";
|
|
2523
|
-
const contentKey = `${p.bubble.title}|${p.bubble.why}|${p.bubble.error?.title ?? ""}|${p.bubble.showNext}`;
|
|
2615
|
+
const contentKey = `${p.bubble.title}|${p.bubble.why}|${p.bubble.error?.title ?? ""}|${p.bubble.status ?? ""}|${p.bubble.showNext}`;
|
|
2524
2616
|
// What the bubble sits beside: the target, with an open list over it (the
|
|
2525
2617
|
// list is what the person is choosing from now). What it must not cover:
|
|
2526
2618
|
// open lists, and the other anchors the step allows (a map step's
|
|
@@ -2643,7 +2735,7 @@ function BubbleBody(p) {
|
|
|
2643
2735
|
const b = p.bubble;
|
|
2644
2736
|
const title = b.error ? b.error.title : b.title;
|
|
2645
2737
|
const why = b.error ? (b.error.detail ?? "") : b.why;
|
|
2646
|
-
return (jsxs(Fragment, { children: [jsxs("div", { className: "eg-b-top", children: [jsxs("span", { className: "eg-mis", children: ["\u0E20\u0E32\u0E23\u0E01\u0E34\u0E08 ", b.missionNo, " \u00B7 ", b.missionTitle, b.replay ? " (ทบทวน)" : ""] }), jsxs("span", { className: "eg-cnt", children: ["\u0E02\u0E31\u0E49\u0E19\u0E17\u0E35\u0E48 ", b.index + 1, "/", b.total] })] }), jsx("div", { className: "eg-pips", "aria-hidden": "true", children: Array.from({ length: b.total }, (_, i) => (jsx("i", { className: i < b.index ? "eg-d" : i === b.index ? "eg-c" : undefined }, i))) }), jsx("p", { className: "eg-b-t", id: BUBBLE_TITLE_ID, children: jsx(GuideTitle, { text: title }) }), jsx("p", { className: "eg-b-w", id: BUBBLE_WHY_ID, hidden: !why, children: jsx(GuideTitle, { text: why }) }), jsxs("div", { className: "eg-b-f", children: [jsx("button", { type: "button", className: "eg-exit", onClick: p.onExit, children: b.replay ? "จบการทบทวน" : "ออกจากไกด์" }), jsxs("span", { style: { display: "flex", gap: 6 }, children: [b.showEndWithoutSave ? (jsx("button", { type: "button", className: "eg-btn eg-ghost", onClick: p.onEndWithoutSave, children: "\u0E08\u0E1A\u0E42\u0E14\u0E22\u0E44\u0E21\u0E48\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01" })) : null, b.showNext ? (jsx("button", { type: "button", className: "eg-btn eg-primary", onClick: p.onNext, children: "\u0E16\u0E31\u0E14\u0E44\u0E1B" })) : null] })] })] }));
|
|
2738
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: "eg-b-top", children: [jsxs("span", { className: "eg-mis", children: ["\u0E20\u0E32\u0E23\u0E01\u0E34\u0E08 ", b.missionNo, " \u00B7 ", b.missionTitle, b.replay ? " (ทบทวน)" : ""] }), jsxs("span", { className: "eg-cnt", children: ["\u0E02\u0E31\u0E49\u0E19\u0E17\u0E35\u0E48 ", b.index + 1, "/", b.total] })] }), jsx("div", { className: "eg-pips", "aria-hidden": "true", children: Array.from({ length: b.total }, (_, i) => (jsx("i", { className: i < b.index ? "eg-d" : i === b.index ? "eg-c" : undefined }, i))) }), jsx("p", { className: "eg-b-t", id: BUBBLE_TITLE_ID, children: jsx(GuideTitle, { text: title }) }), jsx("p", { className: "eg-b-w", id: BUBBLE_WHY_ID, hidden: !why, children: jsx(GuideTitle, { text: why }) }), b.status && !b.error ? (jsx("p", { className: "eg-b-s", role: "status", "data-eg-status": "", children: b.status })) : null, jsxs("div", { className: "eg-b-f", children: [jsx("button", { type: "button", className: "eg-exit", onClick: p.onExit, children: b.replay ? "จบการทบทวน" : "ออกจากไกด์" }), jsxs("span", { style: { display: "flex", gap: 6 }, children: [b.showEndWithoutSave ? (jsx("button", { type: "button", className: "eg-btn eg-ghost", onClick: p.onEndWithoutSave, children: "\u0E08\u0E1A\u0E42\u0E14\u0E22\u0E44\u0E21\u0E48\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01" })) : null, b.showNext ? (jsx("button", { type: "button", className: "eg-btn eg-primary", onClick: p.onNext, children: "\u0E16\u0E31\u0E14\u0E44\u0E1B" })) : null] })] })] }));
|
|
2647
2739
|
}
|
|
2648
2740
|
/** The fingertip at ≈62% across and down the target; upside down near the bottom. */
|
|
2649
2741
|
function handPosition(spot, viewportHeight) {
|
|
@@ -3789,12 +3881,14 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
3789
3881
|
missionNo,
|
|
3790
3882
|
missionTitle: mission.title,
|
|
3791
3883
|
replay: screen.replay,
|
|
3792
|
-
|
|
3793
|
-
|
|
3884
|
+
// Steps this person skips (requires / skipIf) are not counted.
|
|
3885
|
+
index: stepPosition(mission, screen.index, env(), state.progress.context).no - 1,
|
|
3886
|
+
total: stepPosition(mission, screen.index, env(), state.progress.context).total,
|
|
3794
3887
|
title: bridge ? "กดปุ่มเมนู" : resolveText(step.title, stepCtx),
|
|
3795
3888
|
why: bridge ? `เมนูทั้งหมดของ ${suite.appName} อยู่ในนี้` : resolveText(step.why, stepCtx),
|
|
3796
3889
|
error: screen.error,
|
|
3797
|
-
|
|
3890
|
+
status: bridge ? null : screen.status,
|
|
3891
|
+
showNext: !bridge && (kind === "input" || kind === "view" || (kind === "select" && screen.replay && screen.replayNext)),
|
|
3798
3892
|
showEndWithoutSave: !bridge && screen.replay && kind === "commit",
|
|
3799
3893
|
}, hasTarget: bridge || !!step.target, targetPressable: targetPressable(step, bridge), phase: screen.phase, confirmExit: screen.confirmExit, reopenHint: suite.reopenHint, nudge: state.nudge, moving: moving, onExit: () => dispatch({ type: "exit" }), onNext: () => dispatch({ type: "next" }), onEndWithoutSave: () => dispatch({ type: "endWithoutSave" }), onRetry: () => dispatch({ type: "retry" }), onGoResume: () => dispatch({ type: "goResume" }), onExitConfirm: () => dispatch({ type: "exitConfirm" }), onExitCancel: () => dispatch({ type: "exitCancel" }) }));
|
|
3800
3894
|
}
|
|
@@ -3846,5 +3940,5 @@ function useFactsKey(facts) {
|
|
|
3846
3940
|
}, [facts]);
|
|
3847
3941
|
}
|
|
3848
3942
|
|
|
3849
|
-
export { API_EVENT as A, GUIDE_EVENT_PREFIX as G, GUIDE_LAYER_ATTRIBUTE as a, GuideErrorBoundary as b, GuideProgressConflict as c, GuideRuntime as d, apiCallDetail as e, emitGuideEvent as f, fillRoute as g, guideAnchor as h,
|
|
3850
|
-
//# sourceMappingURL=guide-runtime-
|
|
3943
|
+
export { API_EVENT as A, GUIDE_EVENT_PREFIX as G, GUIDE_LAYER_ATTRIBUTE as a, GuideErrorBoundary as b, GuideProgressConflict as c, GuideRuntime as d, apiCallDetail as e, emitGuideEvent as f, fillRoute as g, guideAnchor as h, guideMenuShown as i, matchRoute as j, reportApiEvent as k, loadGuides as l, matchApiPath as m, openGuidePanel as o, reportApiCall as r, storeGuides as s, useGuideEntry as u };
|
|
3944
|
+
//# sourceMappingURL=guide-runtime-C7Z00QwM.esm.js.map
|