selfstore 1.3.0 → 1.5.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/dist/widgets.js CHANGED
@@ -1,4 +1,4 @@
1
- import { connectFlow, shareFlow, joinFlow } from './chunk-3XR3GLR2.js';
1
+ import { connectFlow, shareFlow, joinFlow } from './chunk-I243KZMZ.js';
2
2
  import './chunk-TEIAFKFS.js';
3
3
  import './chunk-SRKUHFRG.js';
4
4
  import './chunk-43BJDRPT.js';
@@ -2921,6 +2921,302 @@ var SelfstoreBackupsElement = class extends FlowWidget {
2921
2921
  };
2922
2922
  var pillStyle = "display:inline-block;font-size:0.72em;font-weight:600;line-height:1.6;padding:0 0.55em;border-radius:999px;border:1px solid var(--_border)";
2923
2923
 
2924
+ // src/widgets/gate.ts
2925
+ var EN6 = {
2926
+ "gate.title": "Where should your data live?",
2927
+ "gate.hint": "It is encrypted on this device. Choose where to keep it and it gets saved there for you from now on.",
2928
+ "gate.fine": "",
2929
+ "gate.defer": "Later - keep it in this browser only",
2930
+ "gate.defer.note": "Data kept only in this browser is lost if its storage is cleared."
2931
+ };
2932
+ var GATE_STYLES = `
2933
+ :host { display: contents; }
2934
+ [part~='gate'] {
2935
+ position: fixed;
2936
+ inset: 0;
2937
+ z-index: var(--selfstore-gate-z, 280);
2938
+ overflow-y: auto;
2939
+ background: var(--selfstore-gate-backdrop, Canvas);
2940
+ display: flex;
2941
+ flex-direction: column;
2942
+ align-items: center;
2943
+ }
2944
+ [part~='gate-card'] {
2945
+ width: 100%;
2946
+ max-width: var(--selfstore-gate-width, 30rem);
2947
+ box-sizing: border-box;
2948
+ padding: calc(2rem + env(safe-area-inset-top)) 1.25rem 1.5rem;
2949
+ display: flex;
2950
+ flex-direction: column;
2951
+ flex: 1 0 auto;
2952
+ gap: var(--_gap);
2953
+ container-type: inline-size;
2954
+ outline: none;
2955
+ }
2956
+ [part~='gate-title'] {
2957
+ margin: 0;
2958
+ font-size: var(--selfstore-gate-title-size, 1.5rem);
2959
+ font-weight: 700;
2960
+ line-height: 1.25;
2961
+ }
2962
+ button[part~='gate-defer'] { align-self: flex-start; text-align: left; }
2963
+ [part~='gate-foot'] {
2964
+ flex-shrink: 0;
2965
+ width: 100%;
2966
+ padding-bottom: env(safe-area-inset-bottom);
2967
+ }
2968
+ `;
2969
+ function connectTagFor(gateTag) {
2970
+ return gateTag.endsWith("-gate") ? `${gateTag.slice(0, -"-gate".length)}-connect` : "selfstore-connect";
2971
+ }
2972
+ var SelfstoreGateElement = class extends FlowWidget {
2973
+ static get observedAttributes() {
2974
+ return ["armed", "deferrable"];
2975
+ }
2976
+ #store = null;
2977
+ #targets = null;
2978
+ #options = {};
2979
+ #icons = {};
2980
+ #recommended = null;
2981
+ #advanced = [];
2982
+ #presets = [];
2983
+ #armed = true;
2984
+ #deferrable = true;
2985
+ #deferred = false;
2986
+ #open = false;
2987
+ #connect = null;
2988
+ defaults() {
2989
+ return EN6;
2990
+ }
2991
+ /** The simple store (anything exposing `flowHost`), or a hand-built FlowHost. */
2992
+ get store() {
2993
+ return this.#store;
2994
+ }
2995
+ set store(v) {
2996
+ this.#store = v;
2997
+ this.wire();
2998
+ }
2999
+ /** The destinations to offer, exactly as <selfstore-connect> takes them.
3000
+ * Read when the gate opens; a change while it is open waits for the next
3001
+ * opening rather than rebuilding a journey under the user. */
3002
+ get targets() {
3003
+ return this.#targets;
3004
+ }
3005
+ set targets(v) {
3006
+ this.#targets = v;
3007
+ }
3008
+ /** Options for the connect flow (`defaultResolution`, `password`, ...).
3009
+ * Same timing as `targets`. */
3010
+ get options() {
3011
+ return this.#options;
3012
+ }
3013
+ set options(v) {
3014
+ this.#options = v ?? {};
3015
+ }
3016
+ get icons() {
3017
+ return this.#icons;
3018
+ }
3019
+ set icons(v) {
3020
+ this.#icons = v ?? {};
3021
+ this.rerender();
3022
+ }
3023
+ get recommended() {
3024
+ return this.#recommended;
3025
+ }
3026
+ set recommended(v) {
3027
+ this.#recommended = v;
3028
+ this.rerender();
3029
+ }
3030
+ get advanced() {
3031
+ return this.#advanced;
3032
+ }
3033
+ set advanced(v) {
3034
+ this.#advanced = v ?? [];
3035
+ this.rerender();
3036
+ }
3037
+ get webdavPresets() {
3038
+ return this.#presets;
3039
+ }
3040
+ set webdavPresets(v) {
3041
+ this.#presets = Array.isArray(v) ? v : [];
3042
+ this.rerender();
3043
+ }
3044
+ /** Whether the app has finished booting. Leave it true for a plain drop-in;
3045
+ * an app that restores its destination asynchronously sets it false until
3046
+ * that settles, so a connected user never glimpses the gate on the way in.
3047
+ * Also the `armed` attribute (`armed="false"` to hold it shut). */
3048
+ get armed() {
3049
+ return this.#armed;
3050
+ }
3051
+ set armed(v) {
3052
+ this.#armed = v;
3053
+ this.sync();
3054
+ }
3055
+ /** Whether to offer the way out (device-only is a working mode, just a
3056
+ * fragile one). Also the `deferrable` attribute. Part of the frame, so it
3057
+ * follows the same timing as `targets`: read when the gate opens. */
3058
+ get deferrable() {
3059
+ return this.#deferrable;
3060
+ }
3061
+ set deferrable(v) {
3062
+ this.#deferrable = v;
3063
+ }
3064
+ /** Set once the user chose to stay device-only; keeps the gate shut for the
3065
+ * rest of the session. Assign false to bring it back (a "choose a
3066
+ * destination" entry somewhere else in the app). */
3067
+ get deferred() {
3068
+ return this.#deferred;
3069
+ }
3070
+ set deferred(v) {
3071
+ this.#deferred = v;
3072
+ this.sync();
3073
+ }
3074
+ /** Whether the gate is currently on screen. */
3075
+ get open() {
3076
+ return this.#open;
3077
+ }
3078
+ /** The connect element the gate built, for programmatic control (its own
3079
+ * `flow` included). Null while the gate is shut. */
3080
+ get connect() {
3081
+ return this.#connect;
3082
+ }
3083
+ constructor() {
3084
+ super();
3085
+ this.root.append(h("style", {}, GATE_STYLES));
3086
+ }
3087
+ attributeChangedCallback(name, _old, value) {
3088
+ if (name === "armed") this.armed = value !== "false";
3089
+ if (name === "deferrable") this.deferrable = value !== "false";
3090
+ }
3091
+ connectedCallback() {
3092
+ if (this.hasAttribute("armed")) this.armed = this.getAttribute("armed") !== "false";
3093
+ if (this.hasAttribute("deferrable")) {
3094
+ this.deferrable = this.getAttribute("deferrable") !== "false";
3095
+ }
3096
+ this.wire();
3097
+ }
3098
+ host() {
3099
+ const s = this.#store;
3100
+ if (!s) return null;
3101
+ return "flowHost" in s ? s.flowHost : s;
3102
+ }
3103
+ wire() {
3104
+ if (!this.isConnected) return;
3105
+ this.unsub?.();
3106
+ this.unsub = null;
3107
+ const host = this.host();
3108
+ if (!host) return;
3109
+ this.unsub = host.engine.subscribe(() => this.sync());
3110
+ this.sync();
3111
+ }
3112
+ /** The library already derives "no durable home yet" - asking the target
3113
+ * kind by hand would miss the ranking and nag over a broken connection. */
3114
+ shouldOpen() {
3115
+ const host = this.host();
3116
+ if (!host || !this.#armed || this.#deferred) return false;
3117
+ return host.engine.state.status.action === "choose-destination";
3118
+ }
3119
+ sync() {
3120
+ const open = this.shouldOpen();
3121
+ if (open === this.#open) return;
3122
+ this.#open = open;
3123
+ if (!open) this.#connect = null;
3124
+ super.rerender();
3125
+ if (open) this.root.querySelector('[part~="gate-card"]')?.focus();
3126
+ }
3127
+ /** Rebuilding the frame would re-append the child connect element, and a
3128
+ * custom element that leaves the DOM and comes back rebuilds its flow: a
3129
+ * journey in progress would be dropped on the floor. So while the gate is
3130
+ * open the frame stands and late cosmetics are pushed into the child.
3131
+ * Set `labels` before `store` for them to reach the frame's own copy. */
3132
+ rerender() {
3133
+ const el = this.#connect;
3134
+ if (!el) {
3135
+ super.rerender();
3136
+ return;
3137
+ }
3138
+ el.labels = this.childLabels();
3139
+ el.icons = this.#icons;
3140
+ el.recommended = this.#recommended;
3141
+ el.advanced = this.#advanced;
3142
+ el.webdavPresets = this.#presets;
3143
+ }
3144
+ /** The gate's title already asks the question; the child's heading would
3145
+ * repeat it. An empty label removes a heading, and a host that wants it
3146
+ * back just says so in its own map. */
3147
+ childLabels() {
3148
+ return { "connect.title": "", ...this.labels };
3149
+ }
3150
+ buildConnect() {
3151
+ const el = document.createElement(
3152
+ connectTagFor(this.tagName.toLowerCase())
3153
+ );
3154
+ el.labels = this.childLabels();
3155
+ el.icons = this.#icons;
3156
+ el.recommended = this.#recommended;
3157
+ el.advanced = this.#advanced;
3158
+ el.webdavPresets = this.#presets;
3159
+ el.options = this.#options;
3160
+ el.targets = this.#targets;
3161
+ el.store = this.#store;
3162
+ return el;
3163
+ }
3164
+ deferButton() {
3165
+ if (!this.#deferrable) return null;
3166
+ const note = this.t("gate.defer.note");
3167
+ return h(
3168
+ "div",
3169
+ {},
3170
+ h(
3171
+ "button",
3172
+ {
3173
+ part: "link gate-defer",
3174
+ type: "button",
3175
+ onclick: () => {
3176
+ this.deferred = true;
3177
+ this.emit("selfstore-gate-deferred");
3178
+ }
3179
+ },
3180
+ this.t("gate.defer")
3181
+ ),
3182
+ note ? h("div", { part: "hint gate-defer-note" }, note) : null
3183
+ );
3184
+ }
3185
+ view(into) {
3186
+ if (!this.#open) return;
3187
+ this.#connect = this.buildConnect();
3188
+ const title = h("h1", { part: "gate-title", id: "gate-title" }, this.t("gate.title"));
3189
+ const card = h("div", {
3190
+ part: "gate-card",
3191
+ tabindex: "-1"
3192
+ });
3193
+ put(
3194
+ card,
3195
+ h("slot", { name: "brand" }),
3196
+ title,
3197
+ this.heading("sub gate-hint", "gate.hint"),
3198
+ this.#connect,
3199
+ h("slot", { name: "extra" }),
3200
+ this.heading("hint gate-fine", "gate.fine"),
3201
+ this.deferButton()
3202
+ );
3203
+ put(
3204
+ into,
3205
+ h(
3206
+ "div",
3207
+ {
3208
+ part: "gate",
3209
+ role: "dialog",
3210
+ "aria-modal": "true",
3211
+ "aria-labelledby": "gate-title"
3212
+ },
3213
+ card,
3214
+ h("div", { part: "gate-foot" }, h("slot", { name: "footer" }))
3215
+ )
3216
+ );
3217
+ }
3218
+ };
3219
+
2924
3220
  // src/entries/widgets.ts
2925
3221
  function defineSelfstoreWidgets(prefix = "selfstore") {
2926
3222
  const define = (name, ctor) => {
@@ -2931,8 +3227,9 @@ function defineSelfstoreWidgets(prefix = "selfstore") {
2931
3227
  define(`${prefix}-join`, SelfstoreJoinElement);
2932
3228
  define(`${prefix}-status`, SelfstoreStatusElement);
2933
3229
  define(`${prefix}-backups`, SelfstoreBackupsElement);
3230
+ define(`${prefix}-gate`, SelfstoreGateElement);
2934
3231
  }
2935
3232
 
2936
- export { FlowWidget, SelfstoreBackupsElement, SelfstoreConnectElement, SelfstoreJoinElement, SelfstoreShareElement, SelfstoreStatusElement, defineSelfstoreWidgets };
3233
+ export { FlowWidget, SelfstoreBackupsElement, SelfstoreConnectElement, SelfstoreGateElement, SelfstoreJoinElement, SelfstoreShareElement, SelfstoreStatusElement, defineSelfstoreWidgets };
2937
3234
  //# sourceMappingURL=widgets.js.map
2938
3235
  //# sourceMappingURL=widgets.js.map