@hybridly/vue 0.4.4 → 0.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/index.cjs CHANGED
@@ -236,19 +236,25 @@ const wrapper = vue.defineComponent({
236
236
  renderDialog()
237
237
  ];
238
238
  }
239
- function renderView() {
240
- utils.debug.adapter("vue:render:view", "Rendering view.");
241
- state.view.value.inheritAttrs = !!state.view.value.inheritAttrs;
242
- const actual = state.view.value?.mounted;
243
- state.view.value.mounted = () => {
239
+ function hijackOnMounted(component, type) {
240
+ if (!component) {
241
+ return;
242
+ }
243
+ const actual = component?.mounted;
244
+ component.mounted = () => {
244
245
  actual?.();
245
246
  vue.nextTick(() => {
246
- utils.debug.adapter("vue:render:view", "Calling mounted callbacks.");
247
+ utils.debug.adapter(`vue:render:${type}`, "Calling mounted callbacks.");
247
248
  while (onMountedCallbacks.length) {
248
249
  onMountedCallbacks.shift()?.();
249
250
  }
250
251
  });
251
252
  };
253
+ }
254
+ function renderView() {
255
+ utils.debug.adapter("vue:render:view", "Rendering view.");
256
+ state.view.value.inheritAttrs = !!state.view.value.inheritAttrs;
257
+ hijackOnMounted(state.view.value, "view");
252
258
  return vue.h(state.view.value, {
253
259
  ...state.properties.value,
254
260
  key: state.viewKey.value
@@ -257,6 +263,7 @@ const wrapper = vue.defineComponent({
257
263
  function renderDialog() {
258
264
  if (dialogStore.state.component.value && dialogStore.state.properties.value) {
259
265
  utils.debug.adapter("vue:render:dialog", "Rendering dialog.");
266
+ hijackOnMounted(dialogStore.state.component.value, "dialog");
260
267
  return vue.h(dialogStore.state.component.value, {
261
268
  ...dialogStore.state.properties.value,
262
269
  key: dialogStore.state.key.value
@@ -310,6 +317,11 @@ function setupDevtools(app) {
310
317
  key: "component",
311
318
  value: state.context.value?.view.component
312
319
  });
320
+ payload.instanceData.state.push({
321
+ type: hybridlyStateType,
322
+ key: "deferred",
323
+ value: state.context.value?.view.deferred
324
+ });
313
325
  payload.instanceData.state.push({
314
326
  type: hybridlyStateType,
315
327
  key: "dialog",
@@ -404,8 +416,8 @@ function viewTransition() {
404
416
  let domUpdated;
405
417
  return {
406
418
  name: "view-transition",
407
- navigating: async ({ isInitial }) => {
408
- if (isInitial) {
419
+ navigating: async ({ type, hasDialog }) => {
420
+ if (type === "initial" || hasDialog) {
409
421
  return;
410
422
  }
411
423
  return new Promise((confirmTransitionStarted) => document.startViewTransition(() => {
@@ -416,6 +428,10 @@ function viewTransition() {
416
428
  mounted: () => {
417
429
  domUpdated?.();
418
430
  domUpdated = void 0;
431
+ },
432
+ navigated: () => {
433
+ domUpdated?.();
434
+ domUpdated = void 0;
419
435
  }
420
436
  };
421
437
  }
@@ -444,12 +460,16 @@ async function initializeHybridly(options = {}) {
444
460
  state.setContext(context);
445
461
  },
446
462
  onViewSwap: async (options2) => {
447
- state.setView(options2.component);
463
+ if (options2.component) {
464
+ onMountedCallbacks.push(() => options2.onMounted?.({ isDialog: false }));
465
+ state.setView(options2.component);
466
+ }
448
467
  state.setProperties(options2.properties);
449
468
  if (!options2.preserveState && !options2.dialog) {
450
469
  state.setViewKey(utils.random());
451
470
  }
452
471
  if (options2.dialog) {
472
+ onMountedCallbacks.push(() => options2.onMounted?.({ isDialog: true }));
453
473
  dialogStore.setComponent(await resolve(options2.dialog.component));
454
474
  dialogStore.setProperties(options2.dialog.properties);
455
475
  dialogStore.setKey(options2.dialog.key);
@@ -524,8 +544,8 @@ async function resolveViewComponent(name, options) {
524
544
  const result = options.components.views.find((view) => name === view.identifier);
525
545
  const path = Object.keys(components).sort((a, b) => a.length - b.length).find((path2) => result ? path2.endsWith(result?.path) : false);
526
546
  if (!result || !path) {
527
- console.warn(`Page component [${name}] not found. Available components: `, options.components.views.map(({ identifier }) => identifier));
528
- utils.showPageComponentErrorModal(name);
547
+ console.warn(`View component [${name}] not found. Available components: `, options.components.views.map(({ identifier }) => identifier));
548
+ utils.showViewComponentErrorModal(name);
529
549
  return;
530
550
  }
531
551
  let component = typeof components[path] === "function" ? await components[path]() : components[path];
@@ -539,6 +559,8 @@ const RouterLink = vue.defineComponent({
539
559
  return (props) => {
540
560
  let data = props.data ?? {};
541
561
  const preloads = props.preload ?? false;
562
+ const preserveScroll = props.preserveScroll;
563
+ const preserveState = props.preserveState;
542
564
  const url = core.makeUrl(props.href ?? "");
543
565
  const method = props.method?.toUpperCase() ?? "GET";
544
566
  const as = typeof props.as === "object" ? props.as : props.as?.toLowerCase() ?? "a";
@@ -578,6 +600,8 @@ Please specify a more appropriate element using the "as" attribute. For example:
578
600
  }
579
601
  core.router.preload(url, {
580
602
  data,
603
+ preserveScroll,
604
+ preserveState,
581
605
  ...props.options
582
606
  });
583
607
  }
@@ -647,6 +671,14 @@ Please specify a more appropriate element using the "as" attribute. For example:
647
671
  preload: {
648
672
  type: [Boolean, String],
649
673
  default: false
674
+ },
675
+ preserveScroll: {
676
+ type: Boolean,
677
+ default: void 0
678
+ },
679
+ preserveState: {
680
+ type: Boolean,
681
+ default: void 0
650
682
  }
651
683
  }
652
684
  });
@@ -880,7 +912,7 @@ function useHistoryState(key, initial) {
880
912
  function useBackForward() {
881
913
  const callbacks = [];
882
914
  core.registerHook("navigated", (options) => {
883
- if (options.isBackForward) {
915
+ if (options.type === "back-forward") {
884
916
  callbacks.forEach((fn) => fn(state.context.value));
885
917
  callbacks.splice(0, callbacks.length);
886
918
  }
@@ -897,33 +929,6 @@ function useBackForward() {
897
929
  };
898
930
  }
899
931
 
900
- function usePaginator(paginator) {
901
- const meta = paginator.meta ?? paginator;
902
- const links = meta.links ?? paginator.links;
903
- const items = links.map((link, index) => {
904
- return {
905
- url: link.url,
906
- label: link.label,
907
- isPage: !isNaN(+link.label),
908
- isPrevious: index === 0,
909
- isNext: index === links.length - 1,
910
- isCurrent: link.active,
911
- isSeparator: link.label === "...",
912
- isActive: !!link.url && !link.active
913
- };
914
- });
915
- const pages = items.filter((item) => item.isPage || item.isSeparator);
916
- const current = items.find((item) => item.isCurrent);
917
- const previous = items.find((item) => item.isPrevious);
918
- const next = items.find((item) => item.isNext);
919
- const first = { ...items[1], isActive: items[1].url !== current?.url, label: "«" };
920
- const last = { ...items[items.length - 1], isActive: items[items.length - 1].url !== current?.url, label: "»" };
921
- const from = meta.from;
922
- const to = meta.to;
923
- const total = meta.total;
924
- return { pages, items, previous, next, first, last, total, from, to };
925
- }
926
-
927
932
  function defineLayout(...args) {
928
933
  const layouts = args[0];
929
934
  const properties = args[1];
@@ -1035,9 +1040,9 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
1035
1040
  function currentFilters() {
1036
1041
  return refinements.value.filters.filter(({ is_active }) => is_active);
1037
1042
  }
1038
- function isSorting(name) {
1043
+ function isSorting(name, direction) {
1039
1044
  if (name) {
1040
- return currentSorts().some((sort) => sort.name === name);
1045
+ return currentSorts().some((sort) => sort.name === name && (direction ? sort.direction === direction : true));
1041
1046
  }
1042
1047
  return currentSorts().length !== 0;
1043
1048
  }
@@ -1080,11 +1085,35 @@ function useRefinements(properties, refinementsKeys, defaultOptions = {}) {
1080
1085
  /**
1081
1086
  * Available filters.
1082
1087
  */
1083
- filters: toReactive(refinements.value.filters),
1088
+ filters: toReactive(refinements.value.filters.map((filter) => ({
1089
+ ...filter,
1090
+ /**
1091
+ * Applies this filter.
1092
+ */
1093
+ apply: (value, options) => applyFilter(filter.name, value, options),
1094
+ /**
1095
+ * Clears this filter.
1096
+ */
1097
+ clear: (options) => clearFilter(filter.name, options)
1098
+ }))),
1084
1099
  /**
1085
1100
  * Available sorts.
1086
1101
  */
1087
- sorts: toReactive(refinements.value.sorts),
1102
+ sorts: toReactive(refinements.value.sorts.map((sort) => ({
1103
+ ...sort,
1104
+ /**
1105
+ * Toggles this sort.
1106
+ */
1107
+ toggle: (options) => toggleSort(sort.name, options),
1108
+ /**
1109
+ * Checks if this sort is active.
1110
+ */
1111
+ isSorting: (direction) => isSorting(sort.name, direction),
1112
+ /**
1113
+ * Clears this sort.
1114
+ */
1115
+ clear: (options) => clearSorts(options)
1116
+ }))),
1088
1117
  /**
1089
1118
  * Gets a filter by name.
1090
1119
  */
@@ -1150,6 +1179,193 @@ function useRoute() {
1150
1179
  };
1151
1180
  }
1152
1181
 
1182
+ function useBulkSelect() {
1183
+ const selection = vue.ref({
1184
+ all: false,
1185
+ only: /* @__PURE__ */ new Set(),
1186
+ except: /* @__PURE__ */ new Set()
1187
+ });
1188
+ function selectAll() {
1189
+ selection.value.all = true;
1190
+ selection.value.only.clear();
1191
+ selection.value.except.clear();
1192
+ }
1193
+ function deselectAll() {
1194
+ selection.value.all = false;
1195
+ selection.value.only.clear();
1196
+ selection.value.except.clear();
1197
+ }
1198
+ function select(...records) {
1199
+ records.forEach((record) => selection.value.except.delete(record));
1200
+ records.forEach((record) => selection.value.only.add(record));
1201
+ }
1202
+ function deselect(...records) {
1203
+ records.forEach((record) => selection.value.except.add(record));
1204
+ records.forEach((record) => selection.value.only.delete(record));
1205
+ }
1206
+ function toggle(record, force) {
1207
+ if (selected(record) || force === false) {
1208
+ return deselect(record);
1209
+ }
1210
+ if (!selected(record) || force === true) {
1211
+ return select(record);
1212
+ }
1213
+ }
1214
+ function selected(record) {
1215
+ if (selection.value.all) {
1216
+ return !selection.value.except.has(record);
1217
+ }
1218
+ return selection.value.only.has(record);
1219
+ }
1220
+ const allSelected = vue.computed(() => {
1221
+ return selection.value.all && selection.value.except.size === 0;
1222
+ });
1223
+ return {
1224
+ allSelected,
1225
+ selectAll,
1226
+ deselectAll,
1227
+ select,
1228
+ deselect,
1229
+ toggle,
1230
+ selected,
1231
+ selection
1232
+ };
1233
+ }
1234
+
1235
+ function useTable(props, key, defaultOptions = {}) {
1236
+ const table = vue.computed(() => props[key]);
1237
+ const bulk = useBulkSelect();
1238
+ const refinements = useRefinements(toReactive(table), "refinements", defaultOptions);
1239
+ function getRecordKey(record) {
1240
+ if (typeof record !== "object") {
1241
+ return record;
1242
+ }
1243
+ if (Reflect.has(record, "__hybridId")) {
1244
+ return Reflect.get(record, "__hybridId");
1245
+ }
1246
+ return Reflect.get(record, table.value.keyName);
1247
+ }
1248
+ function getActionName(action) {
1249
+ return typeof action === "string" ? action : action.name;
1250
+ }
1251
+ async function executeInlineAction(action, record) {
1252
+ return await hybridly.router.navigate({
1253
+ method: "post",
1254
+ url: hybridly.route(table.value.endpoint),
1255
+ preserveState: true,
1256
+ data: {
1257
+ type: "action:inline",
1258
+ action: getActionName(action),
1259
+ tableId: table.value.id,
1260
+ recordId: getRecordKey(record)
1261
+ }
1262
+ });
1263
+ }
1264
+ async function executeBulkAction(action, options) {
1265
+ const actionName = getActionName(action);
1266
+ return await hybridly.router.navigate({
1267
+ method: "post",
1268
+ url: hybridly.route(table.value.endpoint),
1269
+ preserveState: true,
1270
+ data: {
1271
+ type: "action:bulk",
1272
+ action: actionName,
1273
+ tableId: table.value.id,
1274
+ all: bulk.selection.value.all,
1275
+ only: [...bulk.selection.value.only],
1276
+ except: [...bulk.selection.value.except]
1277
+ },
1278
+ hooks: {
1279
+ after: () => {
1280
+ if (options?.deselect === true || table.value.bulkActions.find(({ name }) => name === actionName)?.deselect !== false) {
1281
+ bulk.deselectAll();
1282
+ }
1283
+ }
1284
+ }
1285
+ });
1286
+ }
1287
+ return vue.reactive({
1288
+ /** Selects all records. */
1289
+ selectAll: bulk.selectAll,
1290
+ /** Deselects all records. */
1291
+ deselectAll: bulk.deselectAll,
1292
+ /** Checks if the given record is selected. */
1293
+ isSelected: (record) => bulk.selected(getRecordKey(record)),
1294
+ /** Whether all records are selected. */
1295
+ allSelected: bulk.allSelected,
1296
+ /** The current record selection. */
1297
+ selection: bulk.selection,
1298
+ /** Toggles selection for the given record. */
1299
+ toggle: (record) => bulk.toggle(getRecordKey(record)),
1300
+ /** Selects selection for the given record. */
1301
+ select: (record) => bulk.select(getRecordKey(record)),
1302
+ /** Deselects selection for the given record. */
1303
+ deselect: (record) => bulk.deselect(getRecordKey(record)),
1304
+ /** List of inline actions for this table. */
1305
+ inlineActions: vue.computed(() => table.value.inlineActions.map((action) => ({
1306
+ /** Executes the action. */
1307
+ execute: (record) => executeInlineAction(action.name, record),
1308
+ ...action
1309
+ }))),
1310
+ /** List of bulk actions for this table. */
1311
+ bulkActions: vue.computed(() => table.value.bulkActions.map((action) => ({
1312
+ /** Executes the action. */
1313
+ execute: (options) => executeBulkAction(action.name, options),
1314
+ ...action
1315
+ }))),
1316
+ /** Executes the given inline action for the given record. */
1317
+ executeInlineAction,
1318
+ /** Executes the given bulk action. */
1319
+ executeBulkAction,
1320
+ /** List of columns for this table. */
1321
+ columns: vue.computed(() => table.value.columns.map((column) => ({
1322
+ ...column,
1323
+ /** Toggles sorting for this column. */
1324
+ toggleSort: (options) => refinements.toggleSort(column.name, options),
1325
+ /** Checks whether the column is being sorted. */
1326
+ isSorting: (direction) => refinements.isSorting(column.name, direction),
1327
+ /** Applies the filer for this column. */
1328
+ applyFilter: (value, options) => refinements.applyFilter(column.name, value, options),
1329
+ /** Clears the filter for this column. */
1330
+ clearFilter: (options) => refinements.clearFilter(column.name, options),
1331
+ /** Checks whether the column is sortable. */
1332
+ isSortable: refinements.sorts.find((sort) => sort.name === column.name),
1333
+ /** Checks whether the column is filterable. */
1334
+ isFilterable: refinements.filters.find((filters) => filters.name === column.name)
1335
+ }))),
1336
+ /** List of records for this table. */
1337
+ records: vue.computed(() => table.value.records.map((record) => ({
1338
+ /** The actual record. */
1339
+ record,
1340
+ /** The key of the record. Use this instead of `id`. */
1341
+ key: getRecordKey(record),
1342
+ /** Executes the given inline action. */
1343
+ execute: (action) => executeInlineAction(getActionName(action), getRecordKey(record)),
1344
+ /** Gets the available inline actions. */
1345
+ actions: table.value.inlineActions.map((action) => ({
1346
+ ...action,
1347
+ /** Executes the action. */
1348
+ execute: () => executeInlineAction(action.name, getRecordKey(record))
1349
+ })),
1350
+ /** Selects this record. */
1351
+ select: () => bulk.select(getRecordKey(record)),
1352
+ /** Deselects this record. */
1353
+ deselect: () => bulk.deselect(getRecordKey(record)),
1354
+ /** Toggles the selection for this record. */
1355
+ toggle: (force) => bulk.toggle(getRecordKey(record), force),
1356
+ /** Checks whether this record is selected. */
1357
+ selected: bulk.selected(getRecordKey(record)),
1358
+ /** Gets the value of the record for the specified column. */
1359
+ value: (column) => record[typeof column === "string" ? column : column.name]
1360
+ }))),
1361
+ /**
1362
+ * Paginated meta and links.
1363
+ */
1364
+ paginator: vue.computed(() => table.value.paginator),
1365
+ ...refinements
1366
+ });
1367
+ }
1368
+
1153
1369
  exports.can = core.can;
1154
1370
  exports.route = core.route;
1155
1371
  exports.router = core.router;
@@ -1160,12 +1376,13 @@ exports.initializeHybridly = initializeHybridly;
1160
1376
  exports.registerHook = registerHook;
1161
1377
  exports.setProperty = setProperty;
1162
1378
  exports.useBackForward = useBackForward;
1379
+ exports.useBulkSelect = useBulkSelect;
1163
1380
  exports.useContext = useContext;
1164
1381
  exports.useDialog = useDialog;
1165
1382
  exports.useForm = useForm;
1166
1383
  exports.useHistoryState = useHistoryState;
1167
- exports.usePaginator = usePaginator;
1168
1384
  exports.useProperties = useProperties;
1169
1385
  exports.useProperty = useProperty;
1170
1386
  exports.useRefinements = useRefinements;
1171
1387
  exports.useRoute = useRoute;
1388
+ exports.useTable = useTable;