capacitor-native-tabbar 0.0.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/CapacitorNativeTabbar.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +262 -0
- package/README.ru.md +168 -0
- package/android/build.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/kotlin/com/antonseagull/cap/tabbar/CapTabbar.kt +243 -0
- package/android/src/main/kotlin/com/antonseagull/cap/tabbar/CapTabbarPlugin.kt +87 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/drawable/cap_tabbar_placeholder.xml +11 -0
- package/dist/docs.json +228 -0
- package/dist/esm/definitions.d.ts +63 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +19 -0
- package/dist/esm/web.js +128 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +142 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +145 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CapTabbarPlugin/CapTabbar.swift +246 -0
- package/ios/Sources/CapTabbarPlugin/CapTabbarPlugin.swift +75 -0
- package/ios/Tests/CapTabbarPluginTests/CapTabbarTests.swift +13 -0
- package/package.json +86 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
package com.antonseagull.cap.tabbar
|
|
2
|
+
|
|
3
|
+
import android.content.res.ColorStateList
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.Bitmap
|
|
7
|
+
import android.graphics.BitmapFactory
|
|
8
|
+
import android.graphics.drawable.BitmapDrawable
|
|
9
|
+
import android.graphics.drawable.Drawable
|
|
10
|
+
import android.util.Base64
|
|
11
|
+
import android.util.Log
|
|
12
|
+
import android.view.View
|
|
13
|
+
import android.view.Gravity
|
|
14
|
+
import android.view.ViewGroup
|
|
15
|
+
import androidx.core.content.ContextCompat
|
|
16
|
+
import androidx.core.view.ViewCompat
|
|
17
|
+
import androidx.core.view.WindowInsetsCompat
|
|
18
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
19
|
+
import com.getcapacitor.JSArray
|
|
20
|
+
import com.google.android.material.bottomnavigation.BottomNavigationView
|
|
21
|
+
import com.google.android.material.navigation.NavigationBarView
|
|
22
|
+
|
|
23
|
+
class CapTabbar {
|
|
24
|
+
|
|
25
|
+
private var bottomNav: BottomNavigationView? = null
|
|
26
|
+
private var tabData: List<TabItemData> = emptyList()
|
|
27
|
+
private var selectedId: String = ""
|
|
28
|
+
private var parentView: ViewGroup? = null
|
|
29
|
+
private var onTabSelected: ((String) -> Unit)? = null
|
|
30
|
+
|
|
31
|
+
data class TabItemData(
|
|
32
|
+
val id: String,
|
|
33
|
+
val label: String,
|
|
34
|
+
val base64Icon: String?,
|
|
35
|
+
val base64ActiveIcon: String?
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
fun setTabSelectedCallback(callback: (String) -> Unit) {
|
|
39
|
+
onTabSelected = callback
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private fun decodeBase64ToBitmap(base64: String?): Bitmap? {
|
|
43
|
+
if (base64.isNullOrEmpty()) return null
|
|
44
|
+
return try {
|
|
45
|
+
val cleanBase64 = base64
|
|
46
|
+
.replace("data:image/[^;]+;base64,".toRegex(), "")
|
|
47
|
+
.replace("\\s".toRegex(), "")
|
|
48
|
+
val decodedBytes = Base64.decode(cleanBase64, Base64.DEFAULT)
|
|
49
|
+
BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
|
|
50
|
+
} catch (e: Exception) {
|
|
51
|
+
Log.e("CapTabbar", "Failed to decode base64 image", e)
|
|
52
|
+
null
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Drawable name for bundle: lowercase, spaces/dashes -> underscore */
|
|
57
|
+
private fun drawableNameForId(id: String): String =
|
|
58
|
+
id.lowercase().replace(" ", "_").replace("-", "_").replace(Regex("[^a-z0-9_]"), "_")
|
|
59
|
+
|
|
60
|
+
private fun drawableFromBundle(context: Context, name: String): Drawable? {
|
|
61
|
+
val resId = context.resources.getIdentifier(name, "drawable", context.packageName)
|
|
62
|
+
return if (resId != 0) ContextCompat.getDrawable(context, resId) else null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private fun placeholderDrawable(context: Context): Drawable =
|
|
66
|
+
ContextCompat.getDrawable(context, R.drawable.cap_tabbar_placeholder)!!
|
|
67
|
+
|
|
68
|
+
private fun drawableForTab(context: Context, tab: TabItemData, isSelected: Boolean): Drawable {
|
|
69
|
+
val inactiveB64 = tab.base64Icon
|
|
70
|
+
val activeB64 = tab.base64ActiveIcon ?: tab.base64Icon
|
|
71
|
+
val base64 = if (isSelected) (activeB64 ?: inactiveB64) else (inactiveB64 ?: activeB64)
|
|
72
|
+
base64?.let { b64 ->
|
|
73
|
+
decodeBase64ToBitmap(b64)?.let { bitmap ->
|
|
74
|
+
val sizePx = (24 * context.resources.displayMetrics.density).toInt()
|
|
75
|
+
val scaled = if (bitmap.width != sizePx || bitmap.height != sizePx) {
|
|
76
|
+
Bitmap.createScaledBitmap(bitmap, sizePx, sizePx, true)
|
|
77
|
+
} else bitmap
|
|
78
|
+
val d = BitmapDrawable(context.resources, scaled)
|
|
79
|
+
d.setBounds(0, 0, sizePx, sizePx)
|
|
80
|
+
return d
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
val baseName = drawableNameForId(tab.id)
|
|
84
|
+
val inactiveDrawable = drawableFromBundle(context, baseName)
|
|
85
|
+
val activeDrawable = drawableFromBundle(context, "${baseName}_active")
|
|
86
|
+
val drawable = when {
|
|
87
|
+
isSelected -> activeDrawable ?: inactiveDrawable
|
|
88
|
+
else -> inactiveDrawable ?: activeDrawable
|
|
89
|
+
}
|
|
90
|
+
return drawable ?: placeholderDrawable(context)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private fun parseColorHex(hex: String?): Int? {
|
|
94
|
+
if (hex.isNullOrEmpty()) return null
|
|
95
|
+
val s = hex.trim().removePrefix("#")
|
|
96
|
+
return try {
|
|
97
|
+
when (s.length) {
|
|
98
|
+
6 -> Color.parseColor("#$s")
|
|
99
|
+
8 -> Color.parseColor("#$s")
|
|
100
|
+
else -> null
|
|
101
|
+
}
|
|
102
|
+
} catch (_: Exception) { null }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fun show(parent: ViewGroup, tabs: List<TabItemData>, selectedId: String, labelColor: String? = null, labelColorActive: String? = null) {
|
|
106
|
+
this.parentView = parent
|
|
107
|
+
this.tabData = tabs
|
|
108
|
+
this.selectedId = selectedId
|
|
109
|
+
|
|
110
|
+
removeTabBar()
|
|
111
|
+
|
|
112
|
+
val context = parent.context
|
|
113
|
+
val bottomNav = BottomNavigationView(context)
|
|
114
|
+
this.bottomNav = bottomNav
|
|
115
|
+
|
|
116
|
+
val density = context.resources.displayMetrics.density
|
|
117
|
+
val heightDp = 64 // достаточно для иконки + лейбла
|
|
118
|
+
val baseHeightPx = (heightDp * density).toInt()
|
|
119
|
+
val layoutParams = CoordinatorLayout.LayoutParams(
|
|
120
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
121
|
+
baseHeightPx
|
|
122
|
+
).apply {
|
|
123
|
+
gravity = Gravity.BOTTOM
|
|
124
|
+
}
|
|
125
|
+
bottomNav.layoutParams = layoutParams
|
|
126
|
+
|
|
127
|
+
fun applyBottomInset(view: View, bottomInset: Int) {
|
|
128
|
+
if (bottomInset > 0) {
|
|
129
|
+
view.setPadding(0, 0, 0, bottomInset)
|
|
130
|
+
(view.layoutParams as? ViewGroup.MarginLayoutParams)?.height = baseHeightPx + bottomInset
|
|
131
|
+
view.requestLayout()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
ViewCompat.setOnApplyWindowInsetsListener(bottomNav) { view, insets ->
|
|
135
|
+
val bottomInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
|
|
136
|
+
applyBottomInset(view, bottomInset)
|
|
137
|
+
insets
|
|
138
|
+
}
|
|
139
|
+
bottomNav.post {
|
|
140
|
+
ViewCompat.getRootWindowInsets(bottomNav)?.let { insets ->
|
|
141
|
+
val bottomInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
|
|
142
|
+
applyBottomInset(bottomNav, bottomInset)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
ViewCompat.requestApplyInsets(parent)
|
|
146
|
+
bottomNav.setBackgroundColor(0xFFFFFFFF.toInt())
|
|
147
|
+
bottomNav.itemIconTintList = null
|
|
148
|
+
bottomNav.labelVisibilityMode = NavigationBarView.LABEL_VISIBILITY_LABELED
|
|
149
|
+
bottomNav.itemIconSize = (24 * density).toInt()
|
|
150
|
+
|
|
151
|
+
val inactiveColor = parseColorHex(labelColor) ?: 0xFF000000.toInt()
|
|
152
|
+
val activeColor = parseColorHex(labelColorActive) ?: 0xFF007AFF.toInt()
|
|
153
|
+
bottomNav.itemTextColor = ColorStateList(
|
|
154
|
+
arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf(-android.R.attr.state_checked)),
|
|
155
|
+
intArrayOf(activeColor, inactiveColor)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
val menu = bottomNav.menu
|
|
159
|
+
menu.clear()
|
|
160
|
+
|
|
161
|
+
tabs.forEachIndexed { index, tab ->
|
|
162
|
+
val item = menu.add(0, index, index, tab.label)
|
|
163
|
+
item.icon = drawableForTab(context, tab, tab.id == selectedId)
|
|
164
|
+
item.setCheckable(true)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
bottomNav.selectedItemId = tabs.indexOfFirst { it.id == selectedId }.takeIf { it >= 0 } ?: 0
|
|
168
|
+
|
|
169
|
+
bottomNav.setOnItemSelectedListener { item ->
|
|
170
|
+
val tab = tabs.getOrNull(item.itemId) ?: return@setOnItemSelectedListener false
|
|
171
|
+
this.selectedId = tab.id
|
|
172
|
+
updateIcons()
|
|
173
|
+
onTabSelected?.invoke(tab.id)
|
|
174
|
+
true
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
bottomNav.setOnItemReselectedListener { item ->
|
|
178
|
+
val tab = tabs.getOrNull(item.itemId) ?: return@setOnItemReselectedListener
|
|
179
|
+
this.selectedId = tab.id
|
|
180
|
+
updateIcons()
|
|
181
|
+
onTabSelected?.invoke(tab.id)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
parent.addView(bottomNav)
|
|
185
|
+
bottomNav.post { updateIcons() }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fun hide() {
|
|
189
|
+
removeTabBar()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fun getState(): Pair<Boolean, String> {
|
|
193
|
+
val visible = bottomNav != null
|
|
194
|
+
return Pair(visible, if (visible) selectedId else "")
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fun setSelectedTab(tabId: String) {
|
|
198
|
+
val index = tabData.indexOfFirst { it.id == tabId }
|
|
199
|
+
if (index < 0) return
|
|
200
|
+
if (selectedId == tabId) return
|
|
201
|
+
|
|
202
|
+
selectedId = tabId
|
|
203
|
+
bottomNav?.selectedItemId = index
|
|
204
|
+
updateIcons()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private fun updateIcons() {
|
|
208
|
+
val nav = bottomNav ?: return
|
|
209
|
+
tabData.forEachIndexed { index, tab ->
|
|
210
|
+
val item = nav.menu.findItem(index) ?: return@forEachIndexed
|
|
211
|
+
item.icon = drawableForTab(nav.context, tab, tab.id == selectedId)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private fun removeTabBar() {
|
|
216
|
+
bottomNav?.let {
|
|
217
|
+
(it.parent as? ViewGroup)?.removeView(it)
|
|
218
|
+
}
|
|
219
|
+
bottomNav = null
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
companion object {
|
|
223
|
+
fun parseTabs(jsArray: JSArray): List<TabItemData> {
|
|
224
|
+
val list = mutableListOf<TabItemData>()
|
|
225
|
+
for (i in 0 until jsArray.length()) {
|
|
226
|
+
val obj = jsArray.getJSONObject(i) ?: continue
|
|
227
|
+
val id = obj.getString("id") ?: ""
|
|
228
|
+
if (id.isEmpty()) continue
|
|
229
|
+
val base64Icon = obj.optString("base64_icon").takeIf { it.isNotEmpty() }
|
|
230
|
+
val base64ActiveIcon = obj.optString("base64_active_icon").takeIf { it.isNotEmpty() } ?: base64Icon
|
|
231
|
+
list.add(
|
|
232
|
+
TabItemData(
|
|
233
|
+
id = id,
|
|
234
|
+
label = obj.getString("label") ?: "",
|
|
235
|
+
base64Icon = base64Icon,
|
|
236
|
+
base64ActiveIcon = base64ActiveIcon
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
return list
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
package com.antonseagull.cap.tabbar
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.JSArray
|
|
4
|
+
import com.getcapacitor.JSObject
|
|
5
|
+
import com.getcapacitor.Plugin
|
|
6
|
+
import com.getcapacitor.PluginCall
|
|
7
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
8
|
+
import com.getcapacitor.PluginMethod
|
|
9
|
+
import android.view.ViewGroup
|
|
10
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
11
|
+
|
|
12
|
+
@CapacitorPlugin(name = "CapTabbar")
|
|
13
|
+
class CapTabbarPlugin : Plugin() {
|
|
14
|
+
|
|
15
|
+
private val implementation = CapTabbar()
|
|
16
|
+
|
|
17
|
+
override fun load() {
|
|
18
|
+
implementation.setTabSelectedCallback { tabId ->
|
|
19
|
+
val data = JSObject().apply {
|
|
20
|
+
put("tabId", tabId)
|
|
21
|
+
}
|
|
22
|
+
notifyListeners("tabChange", data)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@PluginMethod
|
|
27
|
+
fun show(call: PluginCall) {
|
|
28
|
+
val tabsArray = call.getArray("tabs")
|
|
29
|
+
val selectedId = call.getString("selectedId")
|
|
30
|
+
|
|
31
|
+
if (tabsArray == null || selectedId == null) {
|
|
32
|
+
call.reject("tabs and selectedId are required")
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
val tabs = CapTabbar.parseTabs(tabsArray)
|
|
37
|
+
if (tabs.isEmpty()) {
|
|
38
|
+
call.reject("At least one tab is required")
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
val labelColor = call.getString("label_color")
|
|
43
|
+
val labelColorActive = call.getString("label_color_active")
|
|
44
|
+
|
|
45
|
+
getActivity().runOnUiThread {
|
|
46
|
+
val webView = bridge.webView
|
|
47
|
+
val parent = webView.parent as? ViewGroup
|
|
48
|
+
if (parent == null || parent !is CoordinatorLayout) {
|
|
49
|
+
call.reject("Could not find parent view to attach tab bar")
|
|
50
|
+
return@runOnUiThread
|
|
51
|
+
}
|
|
52
|
+
implementation.show(parent, tabs, selectedId, labelColor, labelColorActive)
|
|
53
|
+
call.resolve()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@PluginMethod
|
|
58
|
+
fun hide(call: PluginCall) {
|
|
59
|
+
getActivity().runOnUiThread {
|
|
60
|
+
implementation.hide()
|
|
61
|
+
call.resolve()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@PluginMethod
|
|
66
|
+
fun setSelectedTab(call: PluginCall) {
|
|
67
|
+
val tabId = call.getString("tabId")
|
|
68
|
+
if (tabId == null) {
|
|
69
|
+
call.reject("tabId is required")
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
getActivity().runOnUiThread {
|
|
73
|
+
implementation.setSelectedTab(tabId)
|
|
74
|
+
call.resolve()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@PluginMethod
|
|
79
|
+
fun getState(call: PluginCall) {
|
|
80
|
+
val state = implementation.getState()
|
|
81
|
+
val result = JSObject().apply {
|
|
82
|
+
put("visible", state.first)
|
|
83
|
+
put("activeTabId", state.second)
|
|
84
|
+
}
|
|
85
|
+
call.resolve(result)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Placeholder when tab icon is missing - broken_image style -->
|
|
3
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
android:width="24dp"
|
|
5
|
+
android:height="24dp"
|
|
6
|
+
android:viewportWidth="24"
|
|
7
|
+
android:viewportHeight="24">
|
|
8
|
+
<path
|
|
9
|
+
android:fillColor="#999999"
|
|
10
|
+
android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01V5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01V19c0,1.1 -0.9,2 -2,2H5c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
|
|
11
|
+
</vector>
|
package/dist/docs.json
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"name": "CapTabbarPlugin",
|
|
4
|
+
"slug": "captabbarplugin",
|
|
5
|
+
"docs": "",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"methods": [
|
|
8
|
+
{
|
|
9
|
+
"name": "show",
|
|
10
|
+
"signature": "(options: ShowOptions) => Promise<void>",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"name": "options",
|
|
14
|
+
"docs": "",
|
|
15
|
+
"type": "ShowOptions"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"returns": "Promise<void>",
|
|
19
|
+
"tags": [],
|
|
20
|
+
"docs": "Show the native tab bar with the given configuration",
|
|
21
|
+
"complexTypes": [
|
|
22
|
+
"ShowOptions"
|
|
23
|
+
],
|
|
24
|
+
"slug": "show"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "hide",
|
|
28
|
+
"signature": "() => Promise<void>",
|
|
29
|
+
"parameters": [],
|
|
30
|
+
"returns": "Promise<void>",
|
|
31
|
+
"tags": [],
|
|
32
|
+
"docs": "Hide the tab bar",
|
|
33
|
+
"complexTypes": [],
|
|
34
|
+
"slug": "hide"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "setSelectedTab",
|
|
38
|
+
"signature": "(options: SetSelectedTabOptions) => Promise<void>",
|
|
39
|
+
"parameters": [
|
|
40
|
+
{
|
|
41
|
+
"name": "options",
|
|
42
|
+
"docs": "",
|
|
43
|
+
"type": "SetSelectedTabOptions"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"returns": "Promise<void>",
|
|
47
|
+
"tags": [],
|
|
48
|
+
"docs": "Programmatically set the selected tab",
|
|
49
|
+
"complexTypes": [
|
|
50
|
+
"SetSelectedTabOptions"
|
|
51
|
+
],
|
|
52
|
+
"slug": "setselectedtab"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "getState",
|
|
56
|
+
"signature": "() => Promise<GetStateResult>",
|
|
57
|
+
"parameters": [],
|
|
58
|
+
"returns": "Promise<GetStateResult>",
|
|
59
|
+
"tags": [],
|
|
60
|
+
"docs": "Get current state: visible and active tab id",
|
|
61
|
+
"complexTypes": [
|
|
62
|
+
"GetStateResult"
|
|
63
|
+
],
|
|
64
|
+
"slug": "getstate"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "addListener",
|
|
68
|
+
"signature": "(eventName: 'tabChange', listenerFunc: (event: TabChangeEvent) => void) => Promise<{ remove: () => Promise<void>; }>",
|
|
69
|
+
"parameters": [
|
|
70
|
+
{
|
|
71
|
+
"name": "eventName",
|
|
72
|
+
"docs": "",
|
|
73
|
+
"type": "'tabChange'"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "listenerFunc",
|
|
77
|
+
"docs": "",
|
|
78
|
+
"type": "(event: TabChangeEvent) => void"
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"returns": "Promise<{ remove: () => Promise<void>; }>",
|
|
82
|
+
"tags": [],
|
|
83
|
+
"docs": "Add listener for tab change events (when user taps a tab)",
|
|
84
|
+
"complexTypes": [
|
|
85
|
+
"TabChangeEvent"
|
|
86
|
+
],
|
|
87
|
+
"slug": "addlistenertabchange-"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"properties": []
|
|
91
|
+
},
|
|
92
|
+
"interfaces": [
|
|
93
|
+
{
|
|
94
|
+
"name": "ShowOptions",
|
|
95
|
+
"slug": "showoptions",
|
|
96
|
+
"docs": "Icons: 2 variants (base64 has priority over bundle)\n\n1) **Base64** – if `base64_icon` / `base64_active_icon` are set, they are used.\n\n2) **Bundle assets** – if base64 not set, icons are loaded from native resources:\n\n**Android** – `android/app/src/main/res/drawable/`:\n - `{id}.png` or `{id}.xml` – inactive icon\n - `{id}_active.png` or `{id}_active.xml` – active icon\n\n**iOS** – Assets.xcassets: Image Set `{id}` and `{id}_active`.\n\nIf an icon is missing, a placeholder is shown.",
|
|
97
|
+
"tags": [],
|
|
98
|
+
"methods": [],
|
|
99
|
+
"properties": [
|
|
100
|
+
{
|
|
101
|
+
"name": "tabs",
|
|
102
|
+
"tags": [],
|
|
103
|
+
"docs": "Array of tab items to display",
|
|
104
|
+
"complexTypes": [
|
|
105
|
+
"TabItem"
|
|
106
|
+
],
|
|
107
|
+
"type": "TabItem[]"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "selectedId",
|
|
111
|
+
"tags": [],
|
|
112
|
+
"docs": "ID of the initially selected tab",
|
|
113
|
+
"complexTypes": [],
|
|
114
|
+
"type": "string"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "label_color",
|
|
118
|
+
"tags": [],
|
|
119
|
+
"docs": "Label color for inactive tabs (hex, e.g. \"#000000\"). Default: black",
|
|
120
|
+
"complexTypes": [],
|
|
121
|
+
"type": "string | undefined"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "label_color_active",
|
|
125
|
+
"tags": [],
|
|
126
|
+
"docs": "Label color for selected tab (hex, e.g. \"#007AFF\"). Default: blue",
|
|
127
|
+
"complexTypes": [],
|
|
128
|
+
"type": "string | undefined"
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "TabItem",
|
|
134
|
+
"slug": "tabitem",
|
|
135
|
+
"docs": "",
|
|
136
|
+
"tags": [],
|
|
137
|
+
"methods": [],
|
|
138
|
+
"properties": [
|
|
139
|
+
{
|
|
140
|
+
"name": "id",
|
|
141
|
+
"tags": [],
|
|
142
|
+
"docs": "Unique identifier for the tab. Used for bundle assets: {id}.png, {id}_active.png",
|
|
143
|
+
"complexTypes": [],
|
|
144
|
+
"type": "string"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "label",
|
|
148
|
+
"tags": [],
|
|
149
|
+
"docs": "Display label",
|
|
150
|
+
"complexTypes": [],
|
|
151
|
+
"type": "string"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "base64_icon",
|
|
155
|
+
"tags": [],
|
|
156
|
+
"docs": "Base64-encoded image for inactive state. If set, overrides bundle assets",
|
|
157
|
+
"complexTypes": [],
|
|
158
|
+
"type": "string | undefined"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"name": "base64_active_icon",
|
|
162
|
+
"tags": [],
|
|
163
|
+
"docs": "Base64-encoded image for active/selected state. If set, overrides bundle assets",
|
|
164
|
+
"complexTypes": [],
|
|
165
|
+
"type": "string | undefined"
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"name": "SetSelectedTabOptions",
|
|
171
|
+
"slug": "setselectedtaboptions",
|
|
172
|
+
"docs": "",
|
|
173
|
+
"tags": [],
|
|
174
|
+
"methods": [],
|
|
175
|
+
"properties": [
|
|
176
|
+
{
|
|
177
|
+
"name": "tabId",
|
|
178
|
+
"tags": [],
|
|
179
|
+
"docs": "ID of the tab to select",
|
|
180
|
+
"complexTypes": [],
|
|
181
|
+
"type": "string"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "GetStateResult",
|
|
187
|
+
"slug": "getstateresult",
|
|
188
|
+
"docs": "",
|
|
189
|
+
"tags": [],
|
|
190
|
+
"methods": [],
|
|
191
|
+
"properties": [
|
|
192
|
+
{
|
|
193
|
+
"name": "visible",
|
|
194
|
+
"tags": [],
|
|
195
|
+
"docs": "Whether the tab bar is currently shown",
|
|
196
|
+
"complexTypes": [],
|
|
197
|
+
"type": "boolean"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "activeTabId",
|
|
201
|
+
"tags": [],
|
|
202
|
+
"docs": "ID of the active/selected tab (empty when hidden)",
|
|
203
|
+
"complexTypes": [],
|
|
204
|
+
"type": "string"
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "TabChangeEvent",
|
|
210
|
+
"slug": "tabchangeevent",
|
|
211
|
+
"docs": "",
|
|
212
|
+
"tags": [],
|
|
213
|
+
"methods": [],
|
|
214
|
+
"properties": [
|
|
215
|
+
{
|
|
216
|
+
"name": "tabId",
|
|
217
|
+
"tags": [],
|
|
218
|
+
"docs": "ID of the newly selected tab",
|
|
219
|
+
"complexTypes": [],
|
|
220
|
+
"type": "string"
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
"enums": [],
|
|
226
|
+
"typeAliases": [],
|
|
227
|
+
"pluginConfigs": []
|
|
228
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface TabItem {
|
|
2
|
+
/** Unique identifier for the tab. Used for bundle assets: {id}.png, {id}_active.png */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Display label */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Base64-encoded image for inactive state. If set, overrides bundle assets */
|
|
7
|
+
base64_icon?: string;
|
|
8
|
+
/** Base64-encoded image for active/selected state. If set, overrides bundle assets */
|
|
9
|
+
base64_active_icon?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Icons: 2 variants (base64 has priority over bundle)
|
|
13
|
+
*
|
|
14
|
+
* 1) **Base64** – if `base64_icon` / `base64_active_icon` are set, they are used.
|
|
15
|
+
*
|
|
16
|
+
* 2) **Bundle assets** – if base64 not set, icons are loaded from native resources:
|
|
17
|
+
*
|
|
18
|
+
* **Android** – `android/app/src/main/res/drawable/`:
|
|
19
|
+
* - `{id}.png` or `{id}.xml` – inactive icon
|
|
20
|
+
* - `{id}_active.png` or `{id}_active.xml` – active icon
|
|
21
|
+
*
|
|
22
|
+
* **iOS** – Assets.xcassets: Image Set `{id}` and `{id}_active`.
|
|
23
|
+
*
|
|
24
|
+
* If an icon is missing, a placeholder is shown.
|
|
25
|
+
*/
|
|
26
|
+
export interface ShowOptions {
|
|
27
|
+
/** Array of tab items to display */
|
|
28
|
+
tabs: TabItem[];
|
|
29
|
+
/** ID of the initially selected tab */
|
|
30
|
+
selectedId: string;
|
|
31
|
+
/** Label color for inactive tabs (hex, e.g. "#000000"). Default: black */
|
|
32
|
+
label_color?: string;
|
|
33
|
+
/** Label color for selected tab (hex, e.g. "#007AFF"). Default: blue */
|
|
34
|
+
label_color_active?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface SetSelectedTabOptions {
|
|
37
|
+
/** ID of the tab to select */
|
|
38
|
+
tabId: string;
|
|
39
|
+
}
|
|
40
|
+
export interface TabChangeEvent {
|
|
41
|
+
/** ID of the newly selected tab */
|
|
42
|
+
tabId: string;
|
|
43
|
+
}
|
|
44
|
+
export interface GetStateResult {
|
|
45
|
+
/** Whether the tab bar is currently shown */
|
|
46
|
+
visible: boolean;
|
|
47
|
+
/** ID of the active/selected tab (empty when hidden) */
|
|
48
|
+
activeTabId: string;
|
|
49
|
+
}
|
|
50
|
+
export interface CapTabbarPlugin {
|
|
51
|
+
/** Show the native tab bar with the given configuration */
|
|
52
|
+
show(options: ShowOptions): Promise<void>;
|
|
53
|
+
/** Hide the tab bar */
|
|
54
|
+
hide(): Promise<void>;
|
|
55
|
+
/** Programmatically set the selected tab */
|
|
56
|
+
setSelectedTab(options: SetSelectedTabOptions): Promise<void>;
|
|
57
|
+
/** Get current state: visible and active tab id */
|
|
58
|
+
getState(): Promise<GetStateResult>;
|
|
59
|
+
/** Add listener for tab change events (when user taps a tab) */
|
|
60
|
+
addListener(eventName: 'tabChange', listenerFunc: (event: TabChangeEvent) => void): Promise<{
|
|
61
|
+
remove: () => Promise<void>;
|
|
62
|
+
}>;
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface TabItem {\n /** Unique identifier for the tab. Used for bundle assets: {id}.png, {id}_active.png */\n id: string;\n /** Display label */\n label: string;\n /** Base64-encoded image for inactive state. If set, overrides bundle assets */\n base64_icon?: string;\n /** Base64-encoded image for active/selected state. If set, overrides bundle assets */\n base64_active_icon?: string;\n}\n\n/**\n * Icons: 2 variants (base64 has priority over bundle)\n *\n * 1) **Base64** – if `base64_icon` / `base64_active_icon` are set, they are used.\n *\n * 2) **Bundle assets** – if base64 not set, icons are loaded from native resources:\n *\n * **Android** – `android/app/src/main/res/drawable/`:\n * - `{id}.png` or `{id}.xml` – inactive icon\n * - `{id}_active.png` or `{id}_active.xml` – active icon\n *\n * **iOS** – Assets.xcassets: Image Set `{id}` and `{id}_active`.\n *\n * If an icon is missing, a placeholder is shown.\n */\n\nexport interface ShowOptions {\n /** Array of tab items to display */\n tabs: TabItem[];\n /** ID of the initially selected tab */\n selectedId: string;\n /** Label color for inactive tabs (hex, e.g. \"#000000\"). Default: black */\n label_color?: string;\n /** Label color for selected tab (hex, e.g. \"#007AFF\"). Default: blue */\n label_color_active?: string;\n}\n\nexport interface SetSelectedTabOptions {\n /** ID of the tab to select */\n tabId: string;\n}\n\nexport interface TabChangeEvent {\n /** ID of the newly selected tab */\n tabId: string;\n}\n\nexport interface GetStateResult {\n /** Whether the tab bar is currently shown */\n visible: boolean;\n /** ID of the active/selected tab (empty when hidden) */\n activeTabId: string;\n}\n\nexport interface CapTabbarPlugin {\n /** Show the native tab bar with the given configuration */\n show(options: ShowOptions): Promise<void>;\n\n /** Hide the tab bar */\n hide(): Promise<void>;\n\n /** Programmatically set the selected tab */\n setSelectedTab(options: SetSelectedTabOptions): Promise<void>;\n\n /** Get current state: visible and active tab id */\n getState(): Promise<GetStateResult>;\n\n /** Add listener for tab change events (when user taps a tab) */\n addListener(\n eventName: 'tabChange',\n listenerFunc: (event: TabChangeEvent) => void\n ): Promise<{ remove: () => Promise<void> }>;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,SAAS,GAAG,cAAc,CAAkB,WAAW,EAAE;IAC7D,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;CAC7D,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { CapTabbarPlugin } from './definitions';\n\nconst CapTabbar = registerPlugin<CapTabbarPlugin>('CapTabbar', {\n web: () => import('./web').then((m) => new m.CapTabbarWeb()),\n});\n\nexport * from './definitions';\nexport { CapTabbar };\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
import type { CapTabbarPlugin, GetStateResult, ShowOptions, SetSelectedTabOptions } from './definitions';
|
|
3
|
+
export declare class CapTabbarWeb extends WebPlugin implements CapTabbarPlugin {
|
|
4
|
+
private tabBarContainer;
|
|
5
|
+
private tabItems;
|
|
6
|
+
private selectedId;
|
|
7
|
+
private labelColor;
|
|
8
|
+
private labelColorActive;
|
|
9
|
+
private tabElements;
|
|
10
|
+
show(options: ShowOptions): Promise<void>;
|
|
11
|
+
hide(): Promise<void>;
|
|
12
|
+
setSelectedTab(options: SetSelectedTabOptions): Promise<void>;
|
|
13
|
+
getState(): Promise<GetStateResult>;
|
|
14
|
+
private base64ToDataUrl;
|
|
15
|
+
private removeTabBar;
|
|
16
|
+
private createTabBar;
|
|
17
|
+
private handleTabClick;
|
|
18
|
+
private updateTabBarSelection;
|
|
19
|
+
}
|