PageSourceSearch

https://silver-falcon-342272.hostingersite.com/wp-content/plugins/…/js/booking-context.js?ver=1783460063

js silver-falcon-342272.hostingersite.com collected 2026-10-03 04:53:16 UTC 10,239 bytes, 270 lines download raw bytes

1/**
2 * booking-context.js — socle composable du front de réservation.
3 *
4 * Miroir vanilla (servi brut au navigateur, AUCUN build) du contexte partagé
5 * entre un `BookingProvider` et ses enfants (calendar, slots, participants,
6 * summary, checkout…). Trois responsabilités, équivalent vanilla d'un React
7 * Context + store :
8 *   - createStore()        : conteneur d'état pub/sub (généralise BookingState)
9 *   - registre providers   : un store par id de provider → OcoBooking.providers
10 *   - resolveProvider(el)  : résolution robuste enfant → provider (fix bug A5)
11 *
12 * Contrat public stable (cf. docs/component-architecture.md §4,
13 * docs/booking-implementation-spec.md §2). Testé dans
14 * src/lib/__tests__/bookingContext.test.ts (Vitest + jsdom).
15 */
16(function (root, factory) {
17  if (typeof module === 'object' && module.exports) {
18    module.exports = factory();
19  } else {
20    root.OcoBooking = factory();
21  }
22}(typeof self !== 'undefined' ? self : this, function () {
23  'use strict';
24
25  /** Registre global : un store par identifiant de provider. */
26  var providers = {};
27
28  /**
29   * createStore — conteneur d'état pub/sub.
30   * @param {object} [initial] état initial
31   * @returns {{getState:Function, setState:Function, subscribe:Function, notify:Function}}
32   */
33  function createStore(initial) {
34    var state = assign({}, initial);
35    var listeners = [];
36    var events = {}; // bus d'événements nommés (on/emit), distinct des abonnés d'état
37
38    function getState() { return state; }
39
40    function setState(updates) {
41      state = assign(assign({}, state), updates);
42      notify();
43    }
44
45    function notify() {
46      // snapshot : un (dés)abonnement pendant la diffusion ne perturbe pas l'itération
47      var snapshot = listeners.slice();
48      for (var i = 0; i < snapshot.length; i++) {
49        snapshot[i](state);
50      }
51    }
52
53    function subscribe(cb) {
54      if (typeof cb !== 'function') { return function () {}; }
55      listeners.push(cb);
56      cb(state); // appel immédiat avec l'état courant (aligné sur BookingState.subscribe)
57      return function unsubscribe() {
58        var idx = listeners.indexOf(cb);
59        if (idx !== -1) { listeners.splice(idx, 1); }
60      };
61    }
62
63    // --- Bus d'événements nommés (on/emit) ---------------------------------
64    // Canal distinct du flux d'état : porte les intentions ponctuelles (ex.
65    // 'oco:open' émis par le launcher pour ouvrir le flux à une étape précise),
66    // qui ne sont pas un changement d'état durable et ne doivent pas re-rendre
67    // les abonnés via notify().
68    function on(event, cb) {
69      if (!event || typeof cb !== 'function') { return function () {}; }
70      (events[event] || (events[event] = [])).push(cb);
71      return function off() {
72        var arr = events[event];
73        if (!arr) { return; }
74        var idx = arr.indexOf(cb);
75        if (idx !== -1) { arr.splice(idx, 1); }
76      };
77    }
78
79    function emit(event, payload) {
80      var arr = events[event];
81      if (!arr) { return; }
82      var snapshot = arr.slice(); // (dés)abonnement pendant la diffusion sans casser l'itération
83      for (var i = 0; i < snapshot.length; i++) { snapshot[i](payload); }
84    }
85
86    return {
87      getState: getState, setState: setState, subscribe: subscribe, notify: notify,
88      on: on, emit: emit,
89    };
90  }
91
92  /** Enregistre/expose le store d'un provider. */
93  function registerProvider(id, store) {
94    if (id == null || id === '') { return store; }
95    providers[String(id)] = store;
96    return store;
97  }
98  function getProvider(id) {
99    if (id == null || id === '') { return null; }
100    return providers[String(id)] || null;
101  }
102  function unregisterProvider(id) {
103    if (id != null) { delete providers[String(id)]; }
104  }
105
106  /**
107   * resolveProvider — retrouve le store du provider d'un élément enfant.
108   * Robuste à l'aplatissement / déplacement du DOM par Bricks (bug A5) :
109   *   1. imbriqué      : closest('.oco-booking-provider') → data-oco-provider
110   *   2. frère/aplati  : closest('[data-oco-provider-ref]') → registre
111   *   3. cas trivial   : un seul provider enregistré sur la page
112   * @param {Element|null} element
113   * @returns {object|null} store ou null
114   */
115  function resolveProvider(element) {
116    if (!element) { return null; }
117    var hasClosest = typeof element.closest === 'function';
118
119    // 1. ascendance DOM (cas nominal imbriqué)
120    if (hasClosest) {
121      var host = element.closest('.oco-booking-provider');
122      if (host) {
123        var byHost = getProvider(attr(host, 'data-oco-provider'));
124        if (byHost) { return byHost; }
125      }
126    }
127
128    // 2. référence explicite portée par le composant (cas frère / rendu aplati)
129    var refHost = hasClosest ? element.closest('[data-oco-provider-ref]') : null;
130    var byRef = getProvider(attr(refHost || element, 'data-oco-provider-ref'));
131    if (byRef) { return byRef; }
132
133    // 3. un seul provider sur la page → résolution sans ambiguïté
134    var ids = keys(providers);
135    if (ids.length === 1) { return providers[ids[0]]; }
136
137    return null;
138  }
139
140  // --- helpers (sans polyfills, compat navigateurs servis brut) ---
141  function assign(target, src) {
142    if (src) {
143      for (var k in src) {
144        if (Object.prototype.hasOwnProperty.call(src, k)) { target[k] = src[k]; }
145      }
146    }
147    return target;
148  }
149  function keys(o) {
150    var r = [];
151    for (var k in o) { if (Object.prototype.hasOwnProperty.call(o, k)) { r.push(k); } }
152    return r;
153  }
154  function attr(el, name) {
155    return el && typeof el.getAttribute === 'function' ? el.getAttribute(name) : null;
156  }
157
158  // --- Intention de recherche persistée -----------------------------------
159  // Pré-remplit la pill search ET les modales de réservation, et survit au
160  // reload / changement de page. On ne persiste QUE l'intention non sensible
161  // (date, participants, service, durée) — JAMAIS de coordonnées (nom, email,
162  // téléphone). TTL court + validation à la lecture (une date passée est
163  // ignorée) pour ne pas pré-remplir une intention périmée.
164  var INTENT_KEY = 'oco_booking_intent';
165  var INTENT_TTL_MS = 60 * 60 * 1000; // 1 heure
166  var INTENT_FIELDS = ['date', 'pax', 'serviceId', 'duration', 'slot'];
167
168  function globalRef() {
169    if (typeof self !== 'undefined') { return self; }
170    if (typeof window !== 'undefined') { return window; }
171    if (typeof globalThis !== 'undefined') { return globalThis; }
172    return null;
173  }
174  function nowMs() { return (new Date()).getTime(); }
175  function intentTodayIso() {
176    var d = new Date();
177    var m = d.getMonth() + 1, day = d.getDate();
178    return d.getFullYear() + '-' + (m < 10 ? '0' : '') + m + '-' + (day < 10 ? '0' : '') + day;
179  }
180  // Accès localStorage défensif (mode privé / quota / storage désactivé → null).
181  function safeStorage() {
182    try {
183      var g = globalRef();
184      var s = g && g.localStorage;
185      if (!s) { return null; }
186      var probe = '__oco_probe__';
187      s.setItem(probe, '1'); s.removeItem(probe);
188      return s;
189    } catch (e) { return null; }
190  }
191
192  function readIntent() {
193    var s = safeStorage();
194    if (!s) { return {}; }
195    var raw; try { raw = s.getItem(INTENT_KEY); } catch (e) { return {}; }
196    if (!raw) { return {}; }
197    var obj; try { obj = JSON.parse(raw); } catch (e) { obj = null; }
198    if (!obj || typeof obj !== 'object') { try { s.removeItem(INTENT_KEY); } catch (e2) {} return {}; }
199    // TTL : intention périmée → purge + ignore.
200    if (!obj.ts || (nowMs() - obj.ts) > INTENT_TTL_MS) {
201      try { s.removeItem(INTENT_KEY); } catch (e3) {}
202      return {};
203    }
204    var out = {};
205    // Validation à la lecture : on ne ressort que des valeurs encore valides.
206    if (typeof obj.date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(obj.date) && obj.date >= intentTodayIso()) {
207      out.date = obj.date;
208    }
209    if (obj.pax != null) { var p = parseInt(obj.pax, 10); if (!isNaN(p) && p >= 1) { out.pax = p; } }
210    if (obj.serviceId != null && String(obj.serviceId) !== '') { out.serviceId = String(obj.serviceId); }
211    if (obj.duration != null) { var d = parseInt(obj.duration, 10); if (!isNaN(d) && d > 0) { out.duration = d; } }
212    // Créneau choisi (heure de début) : sync barre OBV ↔ modale de réservation.
213    if (obj.slot != null && String(obj.slot) !== '') { out.slot = String(obj.slot); }
214    return out;
215  }
216
217  // Écrit un sous-ensemble de champs d'intention (merge). null/'' efface le champ.
218  function writeIntent(partial) {
219    var s = safeStorage();
220    if (!s || !partial || typeof partial !== 'object') { return; }
221    var cur = {};
222    try { var raw = s.getItem(INTENT_KEY); if (raw) { cur = JSON.parse(raw) || {}; } } catch (e) { cur = {}; }
223    if (!cur || typeof cur !== 'object') { cur = {}; }
224    var touched = false;
225    for (var i = 0; i < INTENT_FIELDS.length; i++) {
226      var k = INTENT_FIELDS[i];
227      if (Object.prototype.hasOwnProperty.call(partial, k)) {
228        var v = partial[k];
229        if (v == null || v === '') { delete cur[k]; } else { cur[k] = v; }
230        touched = true;
231      }
232    }
233    if (!touched) { return; }
234    cur.ts = nowMs();
235    try { s.setItem(INTENT_KEY, JSON.stringify(cur)); } catch (e4) {}
236    // Notifie les consommateurs d'un changement d'intention (ex. le listing
237    // date-first se re-rend quand la pill change date/participants). Bus
238    // découplé : la pill écrit l'intention, le listing l'écoute.
239    try {
240      var g = globalRef();
241      if (g && typeof g.dispatchEvent === 'function' && typeof g.CustomEvent === 'function') {
242        g.dispatchEvent(new g.CustomEvent('oco:intent', { detail: readIntent() }));
243      }
244    } catch (e5) {}
245  }
246
247  function clearIntent() {
248    var s = safeStorage();
249    if (!s) { return; }
250    try { s.removeItem(INTENT_KEY); } catch (e) {}
251  }
252
253  var intent = {
254    read: readIntent,
255    write: writeIntent,
256    clear: clearIntent,
257    KEY: INTENT_KEY,
258    TTL_MS: INTENT_TTL_MS,
259  };
260
261  return {
262    providers: providers,
263    createStore: createStore,
264    registerProvider: registerProvider,
265    getProvider: getProvider,
266    unregisterProvider: unregisterProvider,
267    resolveProvider: resolveProvider,
268    intent: intent,
269  };
270}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.