Skip to content

implementing combobox widget 🔥 #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion demo/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<vn-spinbox />
<vn-text>Here's a progres bar</vn-text>
<vn-progress-bar :value="45" />
<vn-combobox :items="data" />
</vn-view>
</template>

Expand All @@ -31,6 +32,11 @@ export default {
setup() {
const count = ref(0);
const viewVisible = ref(true);
const data = [
{text: "item 1"},
{text: "item 2"},
{text: "item 3"},
];
const line1Text = ref('Type whatever you want in the text box below');
const inc = () => {
count.value++;
Expand All @@ -52,7 +58,8 @@ export default {
viewVisible,
toggleView,
line1Text,
link
link,
data,
};
}
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { createApp } from './renderer';
export { vModelText } from './renderer/directives/vModelText';
export { vModelSlider } from './renderer/directives/vModelSlider';
export { vModelSpinBox } from './renderer/directives/vModelSpinBox';
export { vModelComboBox } from './renderer/directives/vModelComboBox';
export { vShow } from './renderer/directives/vShow';

export * from '@vue/runtime-core';
22 changes: 22 additions & 0 deletions src/renderer/directives/vModelComboBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ObjectDirective } from '@vue/runtime-core';
import { VNComboBox } from '../../widgets/ComboBox/VNComboBox';

type ModelDirective<T> = ObjectDirective<T & { _assign: Function }>

export const vModelComboBox: ModelDirective<VNComboBox> = {
beforeMount: (el, { value }, vnode) => {
el.setCurrentIndex(value);
// eslint-disable-next-line no-param-reassign, no-underscore-dangle
el._assign = vnode.props!['onUpdate:modelValue'] as Function;
el.addEventListener('currentIndexChanged', (indexValue) => {
// eslint-disable-next-line no-underscore-dangle
el._assign(indexValue);
Comment on lines +8 to +13
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used the currentIndex value for implementing v-model – would this be okay?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, index feels safer than the text event.

});
},
beforeUpdate: (el, { value, oldValue }) => {
if (value === oldValue) {
return;
}
el.setCurrentIndex(value);
},
};
5 changes: 5 additions & 0 deletions src/vueLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { isNativeWidget } from './widgets/nativeWidget';
const V_MODEL_TEXT = Symbol('vModelText');
const V_MODEL_SLIDER = Symbol('vModelSlider');
const V_MODEL_SPINBOX = Symbol('vModelSpinBox');
const V_MODEL_COMBOBOX = Symbol('vModelComboBox');

registerRuntimeHelpers({
[V_MODEL_TEXT]: 'vModelText',
[V_MODEL_SLIDER]: 'vModelSlider',
[V_MODEL_SPINBOX]: 'vModelSpinBox',
[V_MODEL_COMBOBOX]: 'vModelComboBox',
});

type CompilerOptions = {
Expand All @@ -35,6 +37,9 @@ export const compilerOptions: CompilerOptions = {
case 'vn-spinbox':
directiveToUse = V_MODEL_SPINBOX;
break;
case 'vn-combobox':
directiveToUse = V_MODEL_COMBOBOX;
break;
default:
throw new Error(`cannot use v-model on tag: ${tag}`);
}
Expand Down
159 changes: 159 additions & 0 deletions src/widgets/ComboBox/VNComboBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import {
QComboBox, NodeWidget, QSize, QVariant, SizeAdjustPolicy, InsertPolicy, QIcon,
} from '@nodegui/nodegui';
import { VNWidget } from 'widgets/config';
import { ViewProps, viewPropsSetters } from '../View/VNView';
import { PropSetters, Prop } from '../../renderer/patchProp';

/**
* The ComboBox component provides the ability to add and manipulate native combobox widgets.
* It is based on [NodeGui's QComboBox](https://docs.nodegui.org/docs/api/generated/classes/qcombobox/).
*
* ## Usage
*
* ```html
* <template>
* <vn-view>
* <vn-text>Choose a color</vn-text>
* <vn-combobox :items="data" v-model="index" />
* <vn-text>You have picked: {{data[index].text}}</vn-text>
* </vn-view>
* </template>
*
* <script>
* import { ref } from 'vue';
*
* export default {
* setup() {
* const index = ref(0);
* const data = [
* {text: "Red"},
* {text: "Yellow"},
* {text: "Blue"},
* {text: "Green"},
* ];
*
* return {
* index,
* data,
* }
* }
* };
* </script>
*
* ```
*
* ## What it looks like?
*
* ![combobox-demo](/img/vn-combobox.gif)
*
* ## Props and styling
*
* You can find all the props `vn-combobox` accepts listed below.
* Apart from this, you can take a look at the [styling](/docs/guides/3-styling)
* and [event handling](/docs/guides/5-handle-events) docs
*/

export interface ComboBoxProps extends ViewProps {
items?: ComboBoxItem[];
count?: number;
iconSize?: QSize;
frame?: boolean;
currentIndex?: number;
currentData?: QVariant;
currentText?: string;
duplicatesEnabled?: boolean;
editable?: boolean;
insertPolicy?: InsertPolicy;
maxCount?: number;
maxVisibleItems?: number;
minimumContentsLength?: number;
modelColumn?: number;
sizeAdjustPolicy?: SizeAdjustPolicy;
}

type ComboBoxItem = {
text: string;
icon?: QIcon;
userData?: QVariant;
};

const comboBoxPropsSetters: PropSetters<VNComboBox, ComboBoxProps> = {
...viewPropsSetters,
items(widget: VNComboBox, _, items: ComboBoxItem[]) {
widget.clear();
items.forEach((item) => {
widget.addItem(item.icon, item.text, item.userData);
});
},
count(widget: VNComboBox, _, count: number) {
widget.setProperty('count', count);
},
iconSize(widget: VNComboBox, _, iconSize: QSize) {
widget.setProperty('iconSize', iconSize.native);
},
frame(widget: VNComboBox, _, frame: boolean) {
widget.setProperty('frame', frame);
},
currentIndex(widget: VNComboBox, _, currentIndex: number) {
widget.setCurrentIndex(currentIndex);
},
currentData(widget: VNComboBox, _, currentData: QVariant) {
widget.setProperty('currentData', currentData.native);
},
currentText(widget: VNComboBox, _, currentText: string) {
widget.setCurrentText(currentText);
},
editable(widget: VNComboBox, _, editable: boolean) {
widget.setEditable(editable);
},
duplicatesEnabled(widget: VNComboBox, _, duplicatesEnabled: boolean) {
widget.setProperty('duplicatesEnabled', duplicatesEnabled);
},
insertPolicy(widget: VNComboBox, _, insertPolicy: InsertPolicy) {
widget.setProperty('insertPolicy', insertPolicy);
},
maxCount(widget: VNComboBox, _, maxCount: number) {
widget.setProperty('maxCount', maxCount);
},
maxVisibleItems(widget: VNComboBox, _, maxVisibleItems: number) {
widget.setMaxVisibleItems(maxVisibleItems);
},
minimumContentsLength(widget: VNComboBox, _, minimumContentsLength: number) {
widget.setProperty('minimumContentsLength', minimumContentsLength);
},
modelColumn(widget: VNComboBox, _, modelColumn: number) {
widget.setProperty('modelColumn', modelColumn);
},
sizeAdjustPolicy(widget: VNComboBox, _, sizeAdjustPolicy: SizeAdjustPolicy) {
widget.setSizeAdjustPolicy(sizeAdjustPolicy);
},
};

/** @internal */
export class VNComboBox extends QComboBox implements VNWidget<ComboBoxProps> {
patchProp(
key: keyof ComboBoxProps,
prevValue: Prop<ComboBoxProps, typeof key>,
nextValue: Prop<ComboBoxProps, typeof key>,
) {
const propSetter = comboBoxPropsSetters[key];
if (propSetter !== undefined) { propSetter(this, prevValue as never, nextValue as never); }
}

insertChild() {
throw new Error('Cannot add child to ComboBox elements');
}

getNextSibling(): NodeWidget<any> | null {
throw new Error('ComboBox elements cannot have children');
}

insertBefore() {
throw new Error('Cannot add child to ComboBox elements');
}

removeChild() {
throw new Error('Cannot remove/add child to ComboBox elements');
}
}
12 changes: 12 additions & 0 deletions src/widgets/ComboBox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { WidgetConfig } from '../config';
import { VNComboBox, ComboBoxProps } from './VNComboBox';

class ComboBoxConfig implements WidgetConfig<ComboBoxProps> {
parentNode: any;

createElement() {
return new VNComboBox();
}
}

export default ComboBoxConfig;
4 changes: 3 additions & 1 deletion src/widgets/nativeWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export type ValidNativeWidgets = 'vn-image' |
'vn-scroll-area' |
'vn-slider' |
'vn-spinbox' |
'vn-progress-bar';
'vn-progress-bar' |
'vn-combobox';

// Add vue-nodegui widgets here
// whenever new ones are created
Expand All @@ -24,6 +25,7 @@ const nativeWidgets: {[key in ValidNativeWidgets]: boolean} = {
'vn-slider': true,
'vn-spinbox': true,
'vn-progress-bar': true,
'vn-combobox': true,
};

export const isNativeWidget = (type: ValidNativeWidgets) => !!nativeWidgets[type];
2 changes: 2 additions & 0 deletions src/widgets/widgetMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ScrollAreaConfig from './ScrollArea';
import SliderConfig from './Slider';
import SpinBoxConfig from './SpinBox';
import ProgressBarConfig from './ProgressBar';
import ComboBoxConfig from './ComboBox';
import { ValidNativeWidgets } from './nativeWidget';
import { WidgetConfig } from './config';

Expand All @@ -28,6 +29,7 @@ const widgetMap: WidgetMap = {
'vn-slider': new SliderConfig(),
'vn-spinbox': new SpinBoxConfig(),
'vn-progress-bar': new ProgressBarConfig(),
'vn-combobox': new ComboBoxConfig(),
};

const getConfigByType = (type: ValidNativeWidgets) => {
Expand Down
Binary file added website/static/img/vn-combobox.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.