@hostlink/nuxt-light 1.66.2 → 1.67.1

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/module.d.mts CHANGED
@@ -2,6 +2,11 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface ModuleOptions {
4
4
  checkPagePermissions?: boolean;
5
+ tableActionIcons?: {
6
+ view?: string;
7
+ edit?: string;
8
+ delete?: string;
9
+ };
5
10
  }
6
11
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
7
12
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.66.2",
4
+ "version": "1.67.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -334,6 +334,16 @@ const module$1 = defineNuxtModule({
334
334
  }, */
335
335
  async setup(options, nuxt) {
336
336
  const resolver = createResolver(import.meta.url);
337
+ nuxt.options.runtimeConfig = nuxt.options.runtimeConfig || {};
338
+ nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
339
+ nuxt.options.runtimeConfig.public.light = {
340
+ ...nuxt.options.runtimeConfig.public.light || {},
341
+ tableActionIcons: {
342
+ view: options.tableActionIcons?.view,
343
+ edit: options.tableActionIcons?.edit,
344
+ delete: options.tableActionIcons?.delete
345
+ }
346
+ };
337
347
  extendPages((pages) => {
338
348
  for (const route of routes) {
339
349
  if (route.children) {
@@ -19,13 +19,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
19
19
  square: boolean;
20
20
  dense: boolean;
21
21
  disable: boolean;
22
+ range: boolean;
22
23
  outlined: boolean;
23
24
  filled: boolean;
24
25
  stackLabel: boolean;
25
26
  standout: string | boolean;
26
27
  hideBottomSpace: boolean;
27
28
  todayBtn: boolean;
28
- range: boolean;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
31
31
  declare const _default: typeof __VLS_export;
@@ -19,13 +19,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
19
19
  square: boolean;
20
20
  dense: boolean;
21
21
  disable: boolean;
22
+ range: boolean;
22
23
  outlined: boolean;
23
24
  filled: boolean;
24
25
  stackLabel: boolean;
25
26
  standout: string | boolean;
26
27
  hideBottomSpace: boolean;
27
28
  todayBtn: boolean;
28
- range: boolean;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
31
31
  declare const _default: typeof __VLS_export;
@@ -1,11 +1,20 @@
1
1
  declare const _default: typeof __VLS_export;
2
2
  export default _default;
3
- declare const __VLS_export: import("vue").DefineComponent<{
3
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
4
+ type __VLS_WithSlots<T, S> = T & (new () => {
5
+ $slots: S;
6
+ });
7
+ declare const __VLS_base: import("vue").DefineComponent<{
8
+ icon?: any;
4
9
  to?: any;
5
10
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
11
  submit: (...args: any[]) => void;
7
12
  }, string, import("vue").PublicProps, Readonly<{
13
+ icon?: any;
8
14
  to?: any;
9
15
  }> & Readonly<{
10
16
  onSubmit?: ((...args: any[]) => any) | undefined;
11
17
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
18
+ type __VLS_Slots = {
19
+ default?: ((props: {}) => any) | undefined;
20
+ };
@@ -2,7 +2,7 @@
2
2
  import { useQuasar } from "quasar";
3
3
  import { useI18n } from "vue-i18n";
4
4
  const { t } = useI18n();
5
- const props = defineProps(["to"]);
5
+ const props = defineProps(["to", "icon"]);
6
6
  const emit = defineEmits(["submit"]);
7
7
  const qua = useQuasar();
8
8
  const onDelete = () => {
@@ -19,5 +19,7 @@ const onDelete = () => {
19
19
  </script>
20
20
 
21
21
  <template>
22
- <q-btn flat round icon="sym_o_delete" @click="onDelete" />
22
+ <q-btn flat round :icon="icon || 'sym_o_delete'" @click="onDelete">
23
+ <slot></slot>
24
+ </q-btn>
23
25
  </template>
@@ -1,11 +1,20 @@
1
1
  declare const _default: typeof __VLS_export;
2
2
  export default _default;
3
- declare const __VLS_export: import("vue").DefineComponent<{
3
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
4
+ type __VLS_WithSlots<T, S> = T & (new () => {
5
+ $slots: S;
6
+ });
7
+ declare const __VLS_base: import("vue").DefineComponent<{
8
+ icon?: any;
4
9
  to?: any;
5
10
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
11
  submit: (...args: any[]) => void;
7
12
  }, string, import("vue").PublicProps, Readonly<{
13
+ icon?: any;
8
14
  to?: any;
9
15
  }> & Readonly<{
10
16
  onSubmit?: ((...args: any[]) => any) | undefined;
11
17
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
18
+ type __VLS_Slots = {
19
+ default?: ((props: {}) => any) | undefined;
20
+ };
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ modelValue?: number | string | {
3
+ min?: number | null;
4
+ max?: number | null;
5
+ } | null;
6
+ placeholder?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ change: (value: any) => any;
10
+ "update:modelValue": (value: any) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onChange?: ((value: any) => any) | undefined;
13
+ "onUpdate:modelValue"?: ((value: any) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -0,0 +1,139 @@
1
+ <script setup>
2
+ import { ref, watch, computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
4
+ const props = defineProps({
5
+ modelValue: { type: [Number, String, Object, null], required: false },
6
+ placeholder: { type: String, required: false }
7
+ });
8
+ const emit = defineEmits(["update:modelValue", "change"]);
9
+ const { t } = useI18n();
10
+ const mode = ref("equals");
11
+ const equalsValue = ref(null);
12
+ const minValue = ref(null);
13
+ const maxValue = ref(null);
14
+ watch(() => props.modelValue, (val) => {
15
+ if (val == null || val === "") {
16
+ equalsValue.value = null;
17
+ minValue.value = null;
18
+ maxValue.value = null;
19
+ } else if (typeof val === "object") {
20
+ mode.value = "range";
21
+ equalsValue.value = null;
22
+ minValue.value = val.min ?? null;
23
+ maxValue.value = val.max ?? null;
24
+ } else {
25
+ mode.value = "equals";
26
+ equalsValue.value = Number(val);
27
+ minValue.value = null;
28
+ maxValue.value = null;
29
+ }
30
+ }, { immediate: true });
31
+ const hasValue = computed(() => {
32
+ if (props.modelValue == null || props.modelValue === "") return false;
33
+ if (typeof props.modelValue === "object") {
34
+ const { min, max } = props.modelValue;
35
+ return min != null || max != null;
36
+ }
37
+ return true;
38
+ });
39
+ const displayLabel = computed(() => {
40
+ if (!hasValue.value) return t("Search");
41
+ if (typeof props.modelValue === "object") {
42
+ const { min, max } = props.modelValue;
43
+ if (min != null && max != null) return `${min} \u2013 ${max}`;
44
+ if (min != null) return `\u2265 ${min}`;
45
+ if (max != null) return `\u2264 ${max}`;
46
+ }
47
+ return `= ${props.modelValue}`;
48
+ });
49
+ const apply = () => {
50
+ let out = null;
51
+ if (mode.value === "equals") {
52
+ out = equalsValue.value;
53
+ } else {
54
+ const outRange = {};
55
+ if (minValue.value != null) outRange.min = minValue.value;
56
+ if (maxValue.value != null) outRange.max = maxValue.value;
57
+ if (Object.keys(outRange).length > 0) out = outRange;
58
+ }
59
+ emit("update:modelValue", out);
60
+ emit("change", out);
61
+ };
62
+ const clear = () => {
63
+ equalsValue.value = null;
64
+ minValue.value = null;
65
+ maxValue.value = null;
66
+ emit("update:modelValue", null);
67
+ emit("change", null);
68
+ };
69
+ </script>
70
+
71
+ <template>
72
+ <q-btn-dropdown
73
+ dense flat square no-caps
74
+ :color="hasValue ? 'primary' : 'grey-7'"
75
+ :label="displayLabel"
76
+ :icon="hasValue ? 'sym_o_filter_alt' : 'sym_o_search'"
77
+ size="sm"
78
+ content-class="l-search-number-dropdown"
79
+ >
80
+ <div class="q-pa-md" style="min-width: 280px">
81
+ <q-tabs
82
+ v-model="mode"
83
+ dense
84
+ no-caps
85
+ align="left"
86
+ active-color="primary"
87
+ indicator-color="primary"
88
+ class="text-grey"
89
+ >
90
+ <q-tab name="equals" :label="t('Equals')" />
91
+ <q-tab name="range" :label="t('Range')" />
92
+ </q-tabs>
93
+
94
+ <q-separator class="q-mb-sm" />
95
+
96
+ <q-tab-panels v-model="mode" animated>
97
+ <q-tab-panel name="equals" class="q-pa-none">
98
+ <q-input
99
+ v-model.number="equalsValue"
100
+ dense filled square
101
+ type="number"
102
+ mask="##########"
103
+ :placeholder="placeholder || t('Enter a number')"
104
+ autofocus
105
+ @keydown.enter.prevent="apply"
106
+ />
107
+ </q-tab-panel>
108
+ <q-tab-panel name="range" class="q-pa-none">
109
+ <div class="row q-gutter-sm items-center">
110
+ <q-input
111
+ v-model.number="minValue"
112
+ dense filled square
113
+ type="number"
114
+ mask="##########"
115
+ :placeholder="t('Min')"
116
+ class="col"
117
+ @keydown.enter.prevent="apply"
118
+ />
119
+ <span class="text-grey">—</span>
120
+ <q-input
121
+ v-model.number="maxValue"
122
+ dense filled square
123
+ type="number"
124
+ mask="##########"
125
+ :placeholder="t('Max')"
126
+ class="col"
127
+ @keydown.enter.prevent="apply"
128
+ />
129
+ </div>
130
+ </q-tab-panel>
131
+ </q-tab-panels>
132
+
133
+ <div class="row q-gutter-sm justify-end q-mt-md">
134
+ <q-btn flat dense :label="t('Clear')" color="negative" @click="clear" />
135
+ <q-btn flat dense :label="t('Apply')" color="primary" @click="apply" />
136
+ </div>
137
+ </div>
138
+ </q-btn-dropdown>
139
+ </template>
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ modelValue?: number | string | {
3
+ min?: number | null;
4
+ max?: number | null;
5
+ } | null;
6
+ placeholder?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ change: (value: any) => any;
10
+ "update:modelValue": (value: any) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onChange?: ((value: any) => any) | undefined;
13
+ "onUpdate:modelValue"?: ((value: any) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -1,10 +1,18 @@
1
1
  import type { Component } from "vue";
2
2
  import { Dialog } from 'quasar';
3
3
  import type { QTableColumn, QTableProps } from 'quasar';
4
+ export interface SearchOptionsContext {
5
+ filters: Record<string, any>;
6
+ columnValue: any;
7
+ column: LTableColumn;
8
+ }
9
+ export type SearchOptionsResolver = (ctx: SearchOptionsContext) => Promise<Record<string, any>[]>;
4
10
  export type LTableColumn = QTableColumn & {
5
11
  searchType?: "date" | "text" | "number" | "select" | "boolean";
6
12
  searchable?: boolean;
7
- searchOptions?: Record<string, any>[] | Function;
13
+ searchPlaceholder?: string;
14
+ searchOptions?: Record<string, any>[] | SearchOptionsResolver;
15
+ searchDependsOn?: string[];
8
16
  searchMultiple?: boolean;
9
17
  searchIndex?: string;
10
18
  component?: Component;
@@ -15,7 +23,7 @@ export type LTableColumn = QTableColumn & {
15
23
  * @deprecated use gql instead
16
24
  */
17
25
  gqlField?: string | Array<string> | Object;
18
- searchMethod?: "equals" | "contains";
26
+ searchMethod?: "equals" | "contains" | "range";
19
27
  autoWidth?: boolean;
20
28
  };
21
29
  export type LTableProps = QTableProps & {
@@ -71,17 +79,17 @@ export interface LTableRequest {
71
79
  }) => void;
72
80
  }
73
81
  declare function requestServerInteraction(): void;
74
- declare var __VLS_114: any, __VLS_117: string, __VLS_118: any, __VLS_149: any, __VLS_152: any, __VLS_315: string, __VLS_316: any;
82
+ declare var __VLS_135: any, __VLS_138: string, __VLS_139: any, __VLS_170: any, __VLS_173: any, __VLS_343: string, __VLS_344: any;
75
83
  type __VLS_Slots = {} & {
76
- [K in NonNullable<typeof __VLS_117>]?: (props: typeof __VLS_118) => any;
84
+ [K in NonNullable<typeof __VLS_138>]?: (props: typeof __VLS_139) => any;
77
85
  } & {
78
- [K in NonNullable<typeof __VLS_315>]?: (props: typeof __VLS_316) => any;
86
+ [K in NonNullable<typeof __VLS_343>]?: (props: typeof __VLS_344) => any;
79
87
  } & {
80
- actions?: (props: typeof __VLS_114) => any;
88
+ actions?: (props: typeof __VLS_135) => any;
81
89
  } & {
82
- 'row-expand'?: (props: typeof __VLS_149) => any;
90
+ 'row-expand'?: (props: typeof __VLS_170) => any;
83
91
  } & {
84
- 'top-right'?: (props: typeof __VLS_152) => any;
92
+ 'top-right'?: (props: typeof __VLS_173) => any;
85
93
  };
86
94
  declare const __VLS_base: import("vue").DefineComponent<LTableProps, {
87
95
  requestServerInteraction: typeof requestServerInteraction;
@@ -149,7 +149,8 @@ const localColumns = computed(
149
149
  ...col,
150
150
  align: col.align ?? "left",
151
151
  field: col.field ?? col.name,
152
- label: t(col.label)
152
+ label: t(col.label),
153
+ searchPlaceholder: col.searchPlaceholder ? t(col.searchPlaceholder) : void 0
153
154
  }))
154
155
  );
155
156
  const emits = defineEmits(["request-data", "update:selected", "delete"]);
@@ -171,15 +172,46 @@ const table = ref();
171
172
  const rows = ref(props.rows ?? []);
172
173
  const resolvedSearchOptions = ref({});
173
174
  const localSearchOptions = ref({});
174
- onMounted(async () => {
175
- for (const col of props.columns ?? []) {
176
- if (!col.searchOptions) continue;
177
- const resolved = typeof col.searchOptions === "function" ? await col.searchOptions() : col.searchOptions;
178
- resolvedSearchOptions.value[col.name] = resolved;
179
- localSearchOptions.value[col.name] = resolved;
175
+ const resolveSeqs = /* @__PURE__ */ new Map();
176
+ const resolveColumnOptions = async (col) => {
177
+ if (!col.searchOptions || !col.name) return;
178
+ if (Array.isArray(col.searchOptions)) {
179
+ resolvedSearchOptions.value[col.name] = col.searchOptions;
180
+ localSearchOptions.value[col.name] = col.searchOptions;
181
+ return;
180
182
  }
183
+ const ctx = {
184
+ filters: { ...filters.value },
185
+ columnValue: filters.value[col.name],
186
+ column: col
187
+ };
188
+ const seq = (resolveSeqs.get(col.name) ?? 0) + 1;
189
+ resolveSeqs.set(col.name, seq);
190
+ try {
191
+ const result = await col.searchOptions(ctx);
192
+ if (seq !== resolveSeqs.get(col.name)) return;
193
+ resolvedSearchOptions.value[col.name] = result;
194
+ localSearchOptions.value[col.name] = result;
195
+ } catch {
196
+ }
197
+ };
198
+ onMounted(async () => {
199
+ await Promise.all(
200
+ (props.columns ?? []).map((col) => resolveColumnOptions(col))
201
+ );
181
202
  table.value?.requestServerInteraction();
182
203
  });
204
+ watch(filters, (newFilters, oldFilters) => {
205
+ if (!props.columns) return;
206
+ for (const col of props.columns) {
207
+ if (!col.searchDependsOn?.length) continue;
208
+ if (typeof col.searchOptions !== "function") continue;
209
+ if (!col.name) continue;
210
+ const deps = col.searchDependsOn;
211
+ const changed = deps.some((dep) => newFilters?.[dep] !== oldFilters?.[dep]);
212
+ if (changed) resolveColumnOptions(col);
213
+ }
214
+ }, { deep: true });
183
215
  const primaryKey = ref(props.rowKey);
184
216
  const modelName = ref(props.modelName);
185
217
  const validateData = () => {
@@ -335,6 +367,18 @@ const getFilterValue = () => {
335
367
  const k = col.searchIndex ?? col.name;
336
368
  if (col.searchType == "boolean") {
337
369
  f[k] = filters.value[col.name];
370
+ } else if (col.searchMethod == "range" && typeof filters.value[col.name] === "object") {
371
+ const rangeValue = filters.value[col.name];
372
+ const rangeFilter = {};
373
+ if (rangeValue.min !== null && rangeValue.min !== "" && rangeValue.min !== void 0) {
374
+ rangeFilter._gte = Number(rangeValue.min);
375
+ }
376
+ if (rangeValue.max !== null && rangeValue.max !== "" && rangeValue.max !== void 0) {
377
+ rangeFilter._lte = Number(rangeValue.max);
378
+ }
379
+ if (Object.keys(rangeFilter).length > 0) {
380
+ f[k] = rangeFilter;
381
+ }
338
382
  } else if (col.searchType == "number") {
339
383
  f[k] = filters.value[col.name];
340
384
  } else if (col.searchType == "date") {
@@ -460,6 +504,9 @@ const localFilterMethod = (rows2, terms) => {
460
504
  if (!String(cell ?? "").toLowerCase().includes(String(value.contains).toLowerCase())) return false;
461
505
  } else if (typeof value === "object" && value !== null && "_between" in value) {
462
506
  if (cell < value._between[0] || cell > value._between[1]) return false;
507
+ } else if (typeof value === "object" && value !== null && ("_gte" in value || "_lte" in value)) {
508
+ if ("_gte" in value && value._gte !== null && value._gte !== void 0 && cell < value._gte) return false;
509
+ if ("_lte" in value && value._lte !== null && value._lte !== void 0 && cell > value._lte) return false;
463
510
  } else {
464
511
  if (cell !== value) return false;
465
512
  }
@@ -549,13 +596,22 @@ const hasFilters = computed(() => {
549
596
  <div :class="{ 'text-grey-8': !isDark }" v-if="primaryKey">
550
597
 
551
598
  <l-action-btn v-if="actionView && props.row.canView"
552
- :to="`/${modelName}/${props.row[primaryKey]}/view`" icon="sym_o_search" />
599
+ :to="`/${modelName}/${props.row[primaryKey]}/view`"
600
+ :icon="light.styles.table.actionIcons.view">
601
+ <q-tooltip>{{ $t("View") }}</q-tooltip>
602
+ </l-action-btn>
553
603
 
554
604
  <l-action-btn v-if="activeEdit && props.row.canUpdate"
555
- :to="`/${modelName}/${props.row[primaryKey]}/edit`" icon="sym_o_edit" />
605
+ :to="`/${modelName}/${props.row[primaryKey]}/edit`"
606
+ :icon="light.styles.table.actionIcons.edit">
607
+ <q-tooltip>{{ $t("Edit") }}</q-tooltip>
608
+ </l-action-btn>
556
609
 
557
610
  <l-delete-btn v-if="actionDelete && props.row.canDelete"
558
- @submit="onDelete(props.row[primaryKey])" size="sm"></l-delete-btn>
611
+ :icon="light.styles.table.actionIcons.delete"
612
+ @submit="onDelete(props.row[primaryKey])" size="sm">
613
+ <q-tooltip>{{ $t("Delete") }}</q-tooltip>
614
+ </l-delete-btn>
559
615
 
560
616
  <slot name="actions" v-bind="props"></slot>
561
617
  </div>
@@ -648,7 +704,14 @@ const hasFilters = computed(() => {
648
704
 
649
705
  <template v-if="col.searchable">
650
706
 
651
- <template v-if="col.searchType == 'number'">
707
+ <template v-if="col.searchMethod == 'range' && (!col.searchType || col.searchType == 'number')">
708
+ <l-search-number
709
+ v-model="filters[col.name]"
710
+ @change="onFilters"
711
+ />
712
+ </template>
713
+
714
+ <template v-else-if="col.searchType == 'number'">
652
715
  <q-input style="min-width: 80px;" dense clearable filled square
653
716
  v-model.number="tempFilters[col.name]"
654
717
  @keydown.enter.prevent="filters[col.name] = tempFilters[col.name]"
@@ -677,9 +740,9 @@ const hasFilters = computed(() => {
677
740
 
678
741
  </template>
679
742
 
680
- <template v-if="!col.searchType || col.searchType == 'text'">
743
+ <template v-if="(!col.searchType || col.searchType == 'text') && col.searchMethod != 'range'">
681
744
  <q-input style="min-width: 80px;" dense clearable filled square
682
- v-model="tempFilters[col.name]" @keydown.enter.prevent="filters[col.name] = tempFilters[col.name]" @clear="delete filters[col.name]" :enterkeyhint="$t('search')" :style="col.searchStyle"></q-input>
745
+ v-model="tempFilters[col.name]" @keydown.enter.prevent="filters[col.name] = tempFilters[col.name]" @clear="delete filters[col.name]" :enterkeyhint="$t('search')" :placeholder="col.searchPlaceholder" :style="col.searchStyle"></q-input>
683
746
 
684
747
  </template>
685
748
 
@@ -1,10 +1,18 @@
1
1
  import type { Component } from "vue";
2
2
  import { Dialog } from 'quasar';
3
3
  import type { QTableColumn, QTableProps } from 'quasar';
4
+ export interface SearchOptionsContext {
5
+ filters: Record<string, any>;
6
+ columnValue: any;
7
+ column: LTableColumn;
8
+ }
9
+ export type SearchOptionsResolver = (ctx: SearchOptionsContext) => Promise<Record<string, any>[]>;
4
10
  export type LTableColumn = QTableColumn & {
5
11
  searchType?: "date" | "text" | "number" | "select" | "boolean";
6
12
  searchable?: boolean;
7
- searchOptions?: Record<string, any>[] | Function;
13
+ searchPlaceholder?: string;
14
+ searchOptions?: Record<string, any>[] | SearchOptionsResolver;
15
+ searchDependsOn?: string[];
8
16
  searchMultiple?: boolean;
9
17
  searchIndex?: string;
10
18
  component?: Component;
@@ -15,7 +23,7 @@ export type LTableColumn = QTableColumn & {
15
23
  * @deprecated use gql instead
16
24
  */
17
25
  gqlField?: string | Array<string> | Object;
18
- searchMethod?: "equals" | "contains";
26
+ searchMethod?: "equals" | "contains" | "range";
19
27
  autoWidth?: boolean;
20
28
  };
21
29
  export type LTableProps = QTableProps & {
@@ -71,17 +79,17 @@ export interface LTableRequest {
71
79
  }) => void;
72
80
  }
73
81
  declare function requestServerInteraction(): void;
74
- declare var __VLS_114: any, __VLS_117: string, __VLS_118: any, __VLS_149: any, __VLS_152: any, __VLS_315: string, __VLS_316: any;
82
+ declare var __VLS_135: any, __VLS_138: string, __VLS_139: any, __VLS_170: any, __VLS_173: any, __VLS_343: string, __VLS_344: any;
75
83
  type __VLS_Slots = {} & {
76
- [K in NonNullable<typeof __VLS_117>]?: (props: typeof __VLS_118) => any;
84
+ [K in NonNullable<typeof __VLS_138>]?: (props: typeof __VLS_139) => any;
77
85
  } & {
78
- [K in NonNullable<typeof __VLS_315>]?: (props: typeof __VLS_316) => any;
86
+ [K in NonNullable<typeof __VLS_343>]?: (props: typeof __VLS_344) => any;
79
87
  } & {
80
- actions?: (props: typeof __VLS_114) => any;
88
+ actions?: (props: typeof __VLS_135) => any;
81
89
  } & {
82
- 'row-expand'?: (props: typeof __VLS_149) => any;
90
+ 'row-expand'?: (props: typeof __VLS_170) => any;
83
91
  } & {
84
- 'top-right'?: (props: typeof __VLS_152) => any;
92
+ 'top-right'?: (props: typeof __VLS_173) => any;
85
93
  };
86
94
  declare const __VLS_base: import("vue").DefineComponent<LTableProps, {
87
95
  requestServerInteraction: typeof requestServerInteraction;
@@ -226,7 +226,6 @@ declare const light: {
226
226
  label: {
227
227
  create: string;
228
228
  set: string;
229
- clear: string;
230
229
  filter: string;
231
230
  remove: string;
232
231
  cancel: string;
@@ -234,6 +233,7 @@ declare const light: {
234
233
  reset: string;
235
234
  select: string;
236
235
  search: string;
236
+ clear: string;
237
237
  ok: string;
238
238
  update: string;
239
239
  refresh: string;
@@ -360,11 +360,11 @@ declare const light: {
360
360
  today: string;
361
361
  };
362
362
  editor: {
363
- size: string;
364
363
  bold: string;
365
364
  left: string;
366
365
  right: string;
367
366
  center: string;
367
+ size: string;
368
368
  align: string;
369
369
  italic: string;
370
370
  strikethrough: string;
@@ -448,8 +448,8 @@ declare const light: {
448
448
  icon: string;
449
449
  };
450
450
  uploader: {
451
- clear: string;
452
451
  done: string;
452
+ clear: string;
453
453
  add: string;
454
454
  upload: string;
455
455
  removeQueue: string;
@@ -823,7 +823,6 @@ declare const _default: () => {
823
823
  label: {
824
824
  create: string;
825
825
  set: string;
826
- clear: string;
827
826
  filter: string;
828
827
  remove: string;
829
828
  cancel: string;
@@ -831,6 +830,7 @@ declare const _default: () => {
831
830
  reset: string;
832
831
  select: string;
833
832
  search: string;
833
+ clear: string;
834
834
  ok: string;
835
835
  update: string;
836
836
  refresh: string;
@@ -957,11 +957,11 @@ declare const _default: () => {
957
957
  today: string;
958
958
  };
959
959
  editor: {
960
- size: string;
961
960
  bold: string;
962
961
  left: string;
963
962
  right: string;
964
963
  center: string;
964
+ size: string;
965
965
  align: string;
966
966
  italic: string;
967
967
  strikethrough: string;
@@ -1045,8 +1045,8 @@ declare const _default: () => {
1045
1045
  icon: string;
1046
1046
  };
1047
1047
  uploader: {
1048
- clear: string;
1049
1048
  done: string;
1049
+ clear: string;
1050
1050
  add: string;
1051
1051
  upload: string;
1052
1052
  removeQueue: string;
@@ -40,7 +40,12 @@ const light = reactive({
40
40
  dense: true,
41
41
  flat: true,
42
42
  bordered: true,
43
- separator: "cell"
43
+ separator: "cell",
44
+ actionIcons: {
45
+ view: "sym_o_search",
46
+ edit: "sym_o_edit",
47
+ delete: "sym_o_delete"
48
+ }
44
49
  },
45
50
  card: {
46
51
  flat: true,
@@ -220,6 +225,10 @@ const light = reactive({
220
225
  light.styles.button = { ...light.styles.button, ...styles.button || {} };
221
226
  light.styles.table = { ...light.styles.table, ...styles.table || {} };
222
227
  light.styles.table.color = light.color;
228
+ light.styles.table.actionIcons = {
229
+ ...light.styles.table.actionIcons,
230
+ ...styles.table?.actionIcons || {}
231
+ };
223
232
  watch(() => light.theme, async () => {
224
233
  await light.setStyle("theme", light.theme);
225
234
  });
@@ -17,7 +17,11 @@ export default defineLightModel({
17
17
  id: {
18
18
  label: "ID",
19
19
  sortable: true,
20
- searchable: true
20
+ searchable: true,
21
+ searchMethod: "range",
22
+ // 啟用 min/max 搜尋
23
+ searchType: "number"
24
+ // 配合 number input
21
25
  },
22
26
  action: {
23
27
  label: "Action",
@@ -17,6 +17,7 @@ export default defineLightModel({
17
17
  label: "Username",
18
18
  sortable: true,
19
19
  searchable: true,
20
+ searchPlaceholder: "Search by username...",
20
21
  required: true
21
22
  },
22
23
  first_name: {
@@ -60,27 +60,37 @@ const rows = computed(() => {
60
60
  const onUpdate = async (value, role, permission) => {
61
61
  const updateKey = `${role}-${permission}`;
62
62
  updating.value.add(updateKey);
63
+ const roleObj = app.value?.roles.find((r) => r.name === role);
64
+ if (!roleObj) {
65
+ updating.value.delete(updateKey);
66
+ return;
67
+ }
68
+ const originalPermissions = [...roleObj.permissions];
69
+ if (value) {
70
+ if (!roleObj.permissions.includes(permission)) {
71
+ roleObj.permissions = [...roleObj.permissions, permission];
72
+ }
73
+ } else {
74
+ if (roleObj.permissions.includes(permission)) {
75
+ roleObj.permissions = roleObj.permissions.filter((p) => p !== permission);
76
+ }
77
+ }
63
78
  try {
64
79
  if (value) {
65
80
  await m("addPermission", { value: permission, role });
66
- $q.notify({
67
- type: "positive",
68
- message: t("Permission granted successfully")
69
- });
70
81
  } else {
71
82
  await m("removePermission", { value: permission, role });
72
- $q.notify({
73
- type: "positive",
74
- message: t("Permission removed successfully")
75
- });
76
83
  }
77
- app.value = await fetchApp();
84
+ $q.notify({
85
+ type: "positive",
86
+ message: t(value ? "Permission granted successfully" : "Permission removed successfully")
87
+ });
78
88
  } catch (error) {
89
+ roleObj.permissions = originalPermissions;
79
90
  $q.notify({
80
91
  type: "negative",
81
92
  message: t("Failed to update permission")
82
93
  });
83
- app.value = await fetchApp();
84
94
  } finally {
85
95
  updating.value.delete(updateKey);
86
96
  }
@@ -94,8 +104,9 @@ const toggleAllForRole = async (role, grant) => {
94
104
  }
95
105
  }
96
106
  if (updates.length === 0) return;
107
+ const originalPermissions = [...role.permissions];
108
+ role.permissions = grant ? [...app.value.permissions] : [];
97
109
  try {
98
- loading.value = true;
99
110
  for (const update of updates) {
100
111
  if (update.grant) {
101
112
  await m("addPermission", { value: update.permission, role: update.role });
@@ -103,18 +114,16 @@ const toggleAllForRole = async (role, grant) => {
103
114
  await m("removePermission", { value: update.permission, role: update.role });
104
115
  }
105
116
  }
106
- app.value = await fetchApp();
107
117
  $q.notify({
108
118
  type: "positive",
109
119
  message: t(grant ? "All permissions granted" : "All permissions removed")
110
120
  });
111
121
  } catch (error) {
122
+ role.permissions = originalPermissions;
112
123
  $q.notify({
113
124
  type: "negative",
114
125
  message: t("Failed to update permissions")
115
126
  });
116
- } finally {
117
- loading.value = false;
118
127
  }
119
128
  };
120
129
  const filter = ref("");
@@ -3,7 +3,7 @@ import "@quasar/quasar-ui-qmarkdown/dist/index.css";
3
3
  import { createClient, setApiClient, defineModel } from "@hostlink/light";
4
4
  import { createI18n } from "vue-i18n";
5
5
  import createLight from "./composables/createLight.js";
6
- import { defineNuxtPlugin, useRoute } from "#app";
6
+ import { defineNuxtPlugin, useRoute, useRuntimeConfig } from "#app";
7
7
  import "./assets/main.css";
8
8
  import message_en from "./locales/en.json";
9
9
  import message_zh from "./locales/zh-hk.json";
@@ -22,14 +22,14 @@ export default defineNuxtPlugin((nuxtApp) => {
22
22
  defineModel("Config", {}).setDataPath("app.listConfig");
23
23
  nuxtApp.vueApp.config.errorHandler = (err, instance, info) => {
24
24
  const $route = useRoute();
25
- const light = useLight();
26
- if (light.devMode) {
25
+ const light2 = useLight();
26
+ if (light2.devMode) {
27
27
  console.log(err);
28
28
  }
29
29
  if (err instanceof Error) {
30
- light.addError(err.message + " at " + $route.fullPath);
30
+ light2.addError(err.message + " at " + $route.fullPath);
31
31
  } else {
32
- light.addError(JSON.stringify(err) + " at " + $route.fullPath);
32
+ light2.addError(JSON.stringify(err) + " at " + $route.fullPath);
33
33
  }
34
34
  };
35
35
  nuxtApp.vueApp.use(QMarkdownVuePlugin);
@@ -44,6 +44,14 @@ export default defineNuxtPlugin((nuxtApp) => {
44
44
  fallbackWarn: false,
45
45
  missingWarn: false
46
46
  });
47
+ const light = useLight();
48
+ const runtimeLight = useRuntimeConfig().public.light;
49
+ if (runtimeLight?.tableActionIcons) {
50
+ const t = runtimeLight.tableActionIcons;
51
+ if (t.view) light.styles.table.actionIcons.view = t.view;
52
+ if (t.edit) light.styles.table.actionIcons.edit = t.edit;
53
+ if (t.delete) light.styles.table.actionIcons.delete = t.delete;
54
+ }
47
55
  nuxtApp.vueApp.use(createLight({
48
56
  i18n
49
57
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostlink/nuxt-light",
3
- "version": "1.66.2",
3
+ "version": "1.67.1",
4
4
  "description": "HostLink Nuxt Light Framework",
5
5
  "repository": {
6
6
  "type": "git",