@frame-kit/ui-ng-patterns 0.1.19 → 0.2.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frame-kit/ui-ng-patterns",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Opinionated, dashboard-ready Angular component patterns composed from @frame-kit/ui-ng.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@angular/forms": "^21.2.0",
|
|
18
18
|
"@angular/platform-browser": "^21.2.0",
|
|
19
19
|
"@angular/router": "^21.2.0",
|
|
20
|
-
"@frame-kit/ui-ng": "0.
|
|
20
|
+
"@frame-kit/ui-ng": "0.2.0",
|
|
21
21
|
"rxjs": "^7.0.0"
|
|
22
22
|
},
|
|
23
23
|
"sideEffects": false,
|
|
@@ -618,8 +618,8 @@ interface OnboardingSubstep {
|
|
|
618
618
|
* - a **group** step — carries `substeps`, and is considered complete once every
|
|
619
619
|
* substep is `done` (the top-level `done` flag is ignored for group steps).
|
|
620
620
|
*
|
|
621
|
-
* All state (`done`) is owned by the consumer; the widget is a
|
|
622
|
-
* of the `steps` model and emits events when the user acts.
|
|
621
|
+
* All state (`done`, `pending`) is owned by the consumer; the widget is a
|
|
622
|
+
* controlled view of the `steps` model and emits events when the user acts.
|
|
623
623
|
*/
|
|
624
624
|
interface OnboardingStep {
|
|
625
625
|
/** Stable identifier, unique within the guide. */
|
|
@@ -628,10 +628,23 @@ interface OnboardingStep {
|
|
|
628
628
|
label: string;
|
|
629
629
|
/** Whether a leaf step is complete. Ignored when `substeps` is set. */
|
|
630
630
|
done: boolean;
|
|
631
|
+
/**
|
|
632
|
+
* The step is in flight — not complete, but expected to complete without
|
|
633
|
+
* further user action (e.g. an invite that auto-completes when accepted).
|
|
634
|
+
* Purely visual: a pending step still counts as incomplete for progress.
|
|
635
|
+
*/
|
|
636
|
+
pending?: boolean;
|
|
637
|
+
/** Optional supporting copy shown while the step is `pending`. */
|
|
638
|
+
pendingNote?: string;
|
|
631
639
|
/** Optional supporting copy shown when the step is expanded. */
|
|
632
640
|
description?: string;
|
|
633
641
|
/** Optional call-to-action label (defaults to a generic "Start"). */
|
|
634
642
|
actionLabel?: string;
|
|
643
|
+
/**
|
|
644
|
+
* Optional secondary text-action label (e.g. "Show help"). When set, the
|
|
645
|
+
* expanded body renders it and clicks emit `stepSecondaryAction`.
|
|
646
|
+
*/
|
|
647
|
+
secondaryActionLabel?: string;
|
|
635
648
|
/** When set, the step renders a sub-checklist and auto-completes when all are done. */
|
|
636
649
|
substeps?: OnboardingSubstep[];
|
|
637
650
|
}
|
|
@@ -648,19 +661,41 @@ interface Point {
|
|
|
648
661
|
/**
|
|
649
662
|
* A floating, draggable onboarding-progress widget. It presents a checklist of
|
|
650
663
|
* steps with a progress bar, collapses to a compact pill with a progress ring,
|
|
651
|
-
* and can be dragged
|
|
652
|
-
*
|
|
664
|
+
* and can be dragged around (CDK drag). It renders over the app in a fixed
|
|
665
|
+
* layer — drop it once near the app root.
|
|
666
|
+
*
|
|
667
|
+
* **Presentational, not clever.** It renders exactly what it is given and
|
|
668
|
+
* reports what the user did — it derives no step state of its own. Which step
|
|
669
|
+
* is open (`openStepId`), which are done or pending (`steps`), whether it is
|
|
670
|
+
* collapsed (`collapsed`): all inputs. When a step should advance, when a
|
|
671
|
+
* context switch should re-focus the guide, when a choice has gone stale —
|
|
672
|
+
* all consumer decisions, because the consumer knows *what changed*, while
|
|
673
|
+
* the widget only ever sees a new array.
|
|
653
674
|
*
|
|
654
675
|
* **Controlled model, no domain data.** The `steps` model is a required,
|
|
655
676
|
* reactive input: the widget is a pure view of it and emits events when the
|
|
656
|
-
* user acts (`stepAction`, `stepMarkDone`, `
|
|
657
|
-
* consumer owns the step state — so if
|
|
658
|
-
*
|
|
659
|
-
* widget never persists step state.
|
|
677
|
+
* user acts (`stepAction`, `stepMarkDone`, `stepSecondaryAction`,
|
|
678
|
+
* `substepToggle`, `footerAction`). The consumer owns the step state — so if
|
|
679
|
+
* it is keyed by, say, the active account or environment, swapping the input
|
|
680
|
+
* re-renders the widget instantly. The widget never persists step state.
|
|
681
|
+
*
|
|
682
|
+
* **Collapsed and openStepId are two-way models.** Bind `[(collapsed)]` /
|
|
683
|
+
* `[(openStepId)]` to drive the card ↔ pill swap and the expanded row from
|
|
684
|
+
* outside; the widget writes them only in response to a direct user action.
|
|
660
685
|
*
|
|
661
|
-
* **
|
|
662
|
-
*
|
|
663
|
-
*
|
|
686
|
+
* **Container-aware.** `boundary` (a selector or element) confines both
|
|
687
|
+
* dragging and the widget's resting position to a region of the app — e.g. a
|
|
688
|
+
* dashboard's content area, so the widget can never cover navigation or a
|
|
689
|
+
* footer. The default resting spot is the boundary's bottom-right corner, and
|
|
690
|
+
* the widget re-clamps itself whenever its own footprint changes (pill ↔ card
|
|
691
|
+
* swap, a step expanding) or the viewport resizes — a corner-parked pill that
|
|
692
|
+
* expands into a large card pulls itself fully back into view. Until the user
|
|
693
|
+
* actually drags it, it stays anchored to the bottom-right corner.
|
|
694
|
+
*
|
|
695
|
+
* **Presentation state is internal + optionally persisted.** Collapsed and
|
|
696
|
+
* drag position persist to `localStorage` under `storageKey`; pass
|
|
697
|
+
* `storageKey = null` for session-only behavior (e.g. when a consumer-side
|
|
698
|
+
* store owns presentation policy).
|
|
664
699
|
*
|
|
665
700
|
* **Opinionated but themeable.** It ships a house look out of the box; all
|
|
666
701
|
* colour flows through `--fk-onboarding-guide-*` tokens, so overriding those
|
|
@@ -670,9 +705,12 @@ interface Point {
|
|
|
670
705
|
* <fk-onboarding-guide
|
|
671
706
|
* [steps]="steps()"
|
|
672
707
|
* title="Setup guide"
|
|
708
|
+
* boundary=".app-content"
|
|
709
|
+
* [storageKey]="null"
|
|
710
|
+
* [(collapsed)]="collapsed"
|
|
673
711
|
* (stepAction)="onStart($event)"
|
|
674
712
|
* (substepToggle)="onToggle($event)"
|
|
675
|
-
* (
|
|
713
|
+
* (footerAction)="onGoLive()"
|
|
676
714
|
* (dismiss)="onDismiss()"
|
|
677
715
|
* />
|
|
678
716
|
* ```
|
|
@@ -685,16 +723,30 @@ declare class OnboardingGuideComponent {
|
|
|
685
723
|
readonly steps: _angular_core.InputSignal<OnboardingStep[]>;
|
|
686
724
|
/** Widget title. */
|
|
687
725
|
readonly title: _angular_core.InputSignal<string>;
|
|
688
|
-
/**
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
726
|
+
/**
|
|
727
|
+
* `localStorage` key for the collapsed + drag-position preferences.
|
|
728
|
+
* `null` disables persistence entirely — presentation state then lives and
|
|
729
|
+
* dies with the component instance (or with whatever the consumer binds).
|
|
730
|
+
*/
|
|
731
|
+
readonly storageKey: _angular_core.InputSignal<string | null>;
|
|
732
|
+
/**
|
|
733
|
+
* Region the widget is confined to: a CSS selector or an element. Both
|
|
734
|
+
* dragging and the widget's resting position are clamped inside it. `null`
|
|
735
|
+
* (the default) confines to the viewport.
|
|
736
|
+
*/
|
|
737
|
+
readonly boundary: _angular_core.InputSignal<string | HTMLElement | null>;
|
|
738
|
+
/** Breathing room kept between the widget and the boundary's edges. */
|
|
739
|
+
readonly boundaryMargin: _angular_core.InputSignal<number>;
|
|
740
|
+
/** Shows the rename (edit) affordance next to the title. */
|
|
741
|
+
readonly renamable: _angular_core.InputSignal<boolean>;
|
|
742
|
+
/** Shows the secondary "mark this step done" action on leaf steps. */
|
|
743
|
+
readonly showMarkDone: _angular_core.InputSignal<boolean>;
|
|
692
744
|
/** Default CTA label when a step doesn't supply its own `actionLabel`. */
|
|
693
745
|
readonly startLabel: _angular_core.InputSignal<string>;
|
|
694
746
|
/** Secondary "mark this step done" action label. */
|
|
695
747
|
readonly markDoneLabel: _angular_core.InputSignal<string>;
|
|
696
|
-
/** Footer
|
|
697
|
-
readonly
|
|
748
|
+
/** Footer action label (e.g. "Go live"). `null` hides the footer action. */
|
|
749
|
+
readonly footerActionLabel: _angular_core.InputSignal<string | null>;
|
|
698
750
|
/** Footer noun for the "{done} of {total} {stepsWord} {doneWord}" summary. */
|
|
699
751
|
readonly stepsWord: _angular_core.InputSignal<string>;
|
|
700
752
|
/** Footer verb for the summary. */
|
|
@@ -715,34 +767,55 @@ declare class OnboardingGuideComponent {
|
|
|
715
767
|
readonly dismissIcon: _angular_core.InputSignal<string>;
|
|
716
768
|
readonly expandIcon: _angular_core.InputSignal<string>;
|
|
717
769
|
readonly actionIcon: _angular_core.InputSignal<string>;
|
|
770
|
+
/**
|
|
771
|
+
* Whether the widget is collapsed to the pill. Two-way bindable so a
|
|
772
|
+
* consumer-side policy (auto-open on context switches, banner-triggered
|
|
773
|
+
* opens) can drive the card ↔ pill swap; unbound, the widget manages it.
|
|
774
|
+
*/
|
|
775
|
+
readonly collapsed: _angular_core.ModelSignal<boolean>;
|
|
718
776
|
/** A step's primary CTA was clicked. Emits the step id. */
|
|
719
777
|
readonly stepAction: _angular_core.OutputEmitterRef<string>;
|
|
720
778
|
/** A step's "mark done" was clicked. Emits the step id. */
|
|
721
779
|
readonly stepMarkDone: _angular_core.OutputEmitterRef<string>;
|
|
780
|
+
/** A step's secondary text-action was clicked. Emits the step id. */
|
|
781
|
+
readonly stepSecondaryAction: _angular_core.OutputEmitterRef<string>;
|
|
722
782
|
/** A substep checkbox was toggled. */
|
|
723
783
|
readonly substepToggle: _angular_core.OutputEmitterRef<OnboardingSubstepToggle>;
|
|
724
|
-
/** The
|
|
725
|
-
readonly
|
|
784
|
+
/** The footer action was invoked. */
|
|
785
|
+
readonly footerAction: _angular_core.OutputEmitterRef<void>;
|
|
726
786
|
/** The rename (edit) affordance was clicked; the consumer owns renaming. */
|
|
727
787
|
readonly renameClick: _angular_core.OutputEmitterRef<void>;
|
|
728
788
|
/** The dismiss control was clicked; the consumer decides whether to hide it. */
|
|
729
789
|
readonly dismiss: _angular_core.OutputEmitterRef<void>;
|
|
730
|
-
/** Fires when the collapsed state changes. */
|
|
731
|
-
readonly collapsedChange: _angular_core.OutputEmitterRef<boolean>;
|
|
732
790
|
readonly className: _angular_core.InputSignal<string>;
|
|
733
791
|
readonly id: _angular_core.InputSignal<string | null>;
|
|
734
|
-
protected readonly collapsed: _angular_core.WritableSignal<boolean>;
|
|
735
792
|
protected readonly dragPosition: _angular_core.WritableSignal<Point>;
|
|
736
793
|
/** True immediately after a real drag, so the drag-handle's click (e.g. the
|
|
737
794
|
* pill's expand) is suppressed rather than firing at the drop point. */
|
|
738
795
|
private justDragged;
|
|
739
796
|
/**
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
797
|
+
* True once the user has deliberately placed the widget (a real drag now,
|
|
798
|
+
* or a persisted position from a previous session). Until then the widget
|
|
799
|
+
* re-anchors to the boundary's bottom-right corner on every viewport or
|
|
800
|
+
* footprint change instead of merely clamping its current spot.
|
|
801
|
+
*/
|
|
802
|
+
private userMoved;
|
|
803
|
+
private footprintObserver;
|
|
804
|
+
/** Guards the one-time preference restore in `hydrateOnce`. */
|
|
805
|
+
private hydrated;
|
|
806
|
+
/** True between CDK's drag start and drop — `reposition` stands down. */
|
|
807
|
+
private dragging;
|
|
808
|
+
/** The boundary element currently under observation (if any). */
|
|
809
|
+
private observedBoundary;
|
|
810
|
+
/**
|
|
811
|
+
* The expanded step, or `null` for none. Fully controlled: the widget
|
|
812
|
+
* writes it only when the user toggles a row, and never infers a value
|
|
813
|
+
* of its own — which step should be open, when to advance to the next
|
|
814
|
+
* one, and whether a context switch should re-focus the guide are all
|
|
815
|
+
* decisions the consumer owns (it knows what changed; the widget only
|
|
816
|
+
* sees a new array).
|
|
744
817
|
*/
|
|
745
|
-
|
|
818
|
+
readonly openStepId: _angular_core.ModelSignal<string | null>;
|
|
746
819
|
protected readonly total: _angular_core.Signal<number>;
|
|
747
820
|
protected readonly doneCount: _angular_core.Signal<number>;
|
|
748
821
|
protected readonly percent: _angular_core.Signal<number>;
|
|
@@ -756,27 +829,67 @@ declare class OnboardingGuideComponent {
|
|
|
756
829
|
get hostId(): string | null;
|
|
757
830
|
constructor();
|
|
758
831
|
isComplete(step: OnboardingStep): boolean;
|
|
832
|
+
isPending(step: OnboardingStep): boolean;
|
|
759
833
|
protected isActive(step: OnboardingStep): boolean;
|
|
760
834
|
protected actionLabelFor(step: OnboardingStep): string;
|
|
761
835
|
protected toggleStep(stepId: string): void;
|
|
762
836
|
protected onAction(step: OnboardingStep): void;
|
|
763
837
|
protected onMarkDone(step: OnboardingStep): void;
|
|
838
|
+
protected onSecondaryAction(step: OnboardingStep): void;
|
|
764
839
|
protected onSubstep(step: OnboardingStep, substep: OnboardingSubstep): void;
|
|
765
840
|
protected onCollapse(): void;
|
|
766
841
|
protected onExpand(): void;
|
|
767
842
|
protected onRename(): void;
|
|
768
843
|
protected onDismiss(): void;
|
|
769
|
-
protected
|
|
844
|
+
protected onFooterAction(): void;
|
|
770
845
|
private setCollapsed;
|
|
771
846
|
private onDragEnded;
|
|
847
|
+
/**
|
|
848
|
+
* Keep the resting position valid as the world changes around it. Two
|
|
849
|
+
* signals feed the same repositioning: the viewport resizing, and the
|
|
850
|
+
* widget's own footprint changing (pill ↔ card swap, a step expanding) —
|
|
851
|
+
* the latter is what stops a corner-parked pill from reopening as a card
|
|
852
|
+
* that's mostly off-screen.
|
|
853
|
+
*/
|
|
854
|
+
private watchGeometry;
|
|
855
|
+
/**
|
|
856
|
+
* The boundary region can resize without a viewport resize — a side
|
|
857
|
+
* panel opening, chrome banners toggling — so the confining element is
|
|
858
|
+
* observed alongside the widget's own footprint. Re-synced whenever the
|
|
859
|
+
* `boundary` input changes.
|
|
860
|
+
*/
|
|
861
|
+
private syncBoundaryObservation;
|
|
862
|
+
private resolveBoundaryElement;
|
|
863
|
+
private readonly onViewportResize;
|
|
864
|
+
private reposition;
|
|
865
|
+
/** The confining region: the `boundary` element's rect, or the viewport. */
|
|
866
|
+
private boundaryRect;
|
|
867
|
+
/**
|
|
868
|
+
* Constrain one axis to `[start + margin, end - size - margin]`.
|
|
869
|
+
*
|
|
870
|
+
* When the region can't fit the widget plus both margins that range is
|
|
871
|
+
* inverted, so the margin is dropped and the widget is fitted to the
|
|
872
|
+
* region's own edges instead — keeping it inside the boundary rather
|
|
873
|
+
* than letting the inset push it out. A widget genuinely larger than
|
|
874
|
+
* its region still overflows (the card has a fixed width and something
|
|
875
|
+
* has to give); it pins to the start edge.
|
|
876
|
+
*/
|
|
877
|
+
private clampAxis;
|
|
772
878
|
private clamp;
|
|
773
|
-
private hydrate;
|
|
774
879
|
private defaultBottomRight;
|
|
880
|
+
/**
|
|
881
|
+
* One-time restore of the persisted preferences, run from the first
|
|
882
|
+
* execution of the boundary effect — inputs are set by then, storage
|
|
883
|
+
* reads are synchronous, and it lands during initial change detection
|
|
884
|
+
* so the restored state is what the first paint shows. The caller's
|
|
885
|
+
* `reposition()` clamps the raw restored position right afterwards.
|
|
886
|
+
*/
|
|
887
|
+
private hydrateOnce;
|
|
775
888
|
private read;
|
|
776
889
|
private persist;
|
|
777
890
|
private storage;
|
|
778
891
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<OnboardingGuideComponent, never>;
|
|
779
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<OnboardingGuideComponent, "fk-onboarding-guide", never, { "steps": { "alias": "steps"; "required": true; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "storageKey": { "alias": "storageKey"; "required": false; "isSignal": true; }; "
|
|
892
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<OnboardingGuideComponent, "fk-onboarding-guide", never, { "steps": { "alias": "steps"; "required": true; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "storageKey": { "alias": "storageKey"; "required": false; "isSignal": true; }; "boundary": { "alias": "boundary"; "required": false; "isSignal": true; }; "boundaryMargin": { "alias": "boundaryMargin"; "required": false; "isSignal": true; }; "renamable": { "alias": "renamable"; "required": false; "isSignal": true; }; "showMarkDone": { "alias": "showMarkDone"; "required": false; "isSignal": true; }; "startLabel": { "alias": "startLabel"; "required": false; "isSignal": true; }; "markDoneLabel": { "alias": "markDoneLabel"; "required": false; "isSignal": true; }; "footerActionLabel": { "alias": "footerActionLabel"; "required": false; "isSignal": true; }; "stepsWord": { "alias": "stepsWord"; "required": false; "isSignal": true; }; "doneWord": { "alias": "doneWord"; "required": false; "isSignal": true; }; "stepsLeftWord": { "alias": "stepsLeftWord"; "required": false; "isSignal": true; }; "allDoneLabel": { "alias": "allDoneLabel"; "required": false; "isSignal": true; }; "renameLabel": { "alias": "renameLabel"; "required": false; "isSignal": true; }; "collapseLabel": { "alias": "collapseLabel"; "required": false; "isSignal": true; }; "dismissLabel": { "alias": "dismissLabel"; "required": false; "isSignal": true; }; "expandLabel": { "alias": "expandLabel"; "required": false; "isSignal": true; }; "checkIcon": { "alias": "checkIcon"; "required": false; "isSignal": true; }; "caretIcon": { "alias": "caretIcon"; "required": false; "isSignal": true; }; "editIcon": { "alias": "editIcon"; "required": false; "isSignal": true; }; "collapseIcon": { "alias": "collapseIcon"; "required": false; "isSignal": true; }; "dismissIcon": { "alias": "dismissIcon"; "required": false; "isSignal": true; }; "expandIcon": { "alias": "expandIcon"; "required": false; "isSignal": true; }; "actionIcon": { "alias": "actionIcon"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "openStepId": { "alias": "openStepId"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; "stepAction": "stepAction"; "stepMarkDone": "stepMarkDone"; "stepSecondaryAction": "stepSecondaryAction"; "substepToggle": "substepToggle"; "footerAction": "footerAction"; "renameClick": "renameClick"; "dismiss": "dismiss"; "openStepId": "openStepIdChange"; }, never, never, true, [{ directive: typeof i1.CdkDrag; inputs: {}; outputs: {}; }]>;
|
|
780
893
|
}
|
|
781
894
|
|
|
782
895
|
/**
|