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